Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 (function() { | 1 (function() { |
| 2 var Utility = { | |
| 3 distance: function(x1, y1, x2, y2) { | |
| 4 var xDelta = (x1 - x2); | |
| 5 var yDelta = (y1 - y2); | |
| 6 | 2 |
| 7 return Math.sqrt(xDelta * xDelta + yDelta * yDelta); | 3 var MAX_RADIUS_PX = 300; |
|
dpapad
2017/02/22 21:07:26
There are references in this file still using the
Dan Beam
2017/02/23 00:23:43
Done.
| |
| 8 }, | 4 var MIN_DURATION_MS = 800; |
| 9 | 5 |
| 10 now: window.performance && window.performance.now ? | 6 /** |
| 11 window.performance.now.bind(window.performance) : Date.now | 7 * @param {number} x1 |
| 12 }; | 8 * @param {number} y1 |
| 9 * @param {number} x2 | |
| 10 * @param {number} y2 | |
| 11 * @return The distance between (x1, y1) and (x2, y2). | |
| 12 */ | |
| 13 var distance = function(x1, y1, x2, y2) { | |
| 14 var xDelta = x1 - x2; | |
| 15 var yDelta = y1 - y2; | |
| 16 return Math.sqrt(xDelta * xDelta + yDelta * yDelta); | |
| 17 }; | |
| 13 | 18 |
| 14 /** | 19 Polymer({ |
| 15 * @param {HTMLElement} element | 20 is: 'paper-ripple', |
| 16 * @constructor | |
| 17 */ | |
| 18 function ElementMetrics(element) { | |
| 19 this.element = element; | |
| 20 this.width = this.boundingRect.width; | |
| 21 this.height = this.boundingRect.height; | |
| 22 | 21 |
| 23 this.size = Math.max(this.width, this.height); | 22 behaviors: [Polymer.IronA11yKeysBehavior], |
| 23 | |
| 24 properties: { | |
| 25 center: {type: Boolean, value: false}, | |
| 26 holdDown: {type: Boolean, value: false, observer: '_holdDownChanged'}, | |
| 27 recenters: {type: Boolean, value: false}, | |
| 28 noink: {type: Boolean, value: false}, | |
| 29 }, | |
| 30 | |
| 31 keyBindings: { | |
| 32 'enter:keydown': '_onEnterKeydown', | |
| 33 'space:keydown': '_onSpaceKeydown', | |
| 34 'space:keyup': '_onSpaceKeyup', | |
| 35 }, | |
| 36 | |
| 37 /** @override */ | |
| 38 created: function() { | |
| 39 /** @type {Array<!Element>} */ | |
| 40 this.ripples = []; | |
| 41 }, | |
| 42 | |
| 43 /** @override */ | |
| 44 attached: function() { | |
| 45 this.keyEventTarget = this.parentNode.nodeType == 11 ? | |
| 46 Polymer.dom(this).getOwnerRoot().host : this.parentNode; | |
| 47 this.listen(this.keyEventTarget, 'up', 'uiUpAction'); | |
| 48 this.listen(this.keyEventTarget, 'down', 'uiDownAction'); | |
| 49 }, | |
| 50 | |
| 51 /** @override */ | |
| 52 detached: function() { | |
| 53 this.unlisten(this.keyEventTarget, 'up', 'uiUpAction'); | |
| 54 this.unlisten(this.keyEventTarget, 'down', 'uiDownAction'); | |
| 55 this.keyEventTarget = null; | |
| 56 }, | |
| 57 | |
| 58 simulatedRipple: function() { | |
| 59 this.downAction(); | |
| 60 // Using a 1ms delay ensures a macro-task. | |
| 61 this.async(function() { this.upAction(); }.bind(this), 1); | |
| 62 }, | |
| 63 | |
| 64 /** @param {Event=} e */ | |
|
dpapad
2017/02/22 21:07:26
Does this pass compilation? IIRC optional params m
Dan Beam
2017/02/23 00:23:43
that's no longer the case
"not prefixed with opt_
| |
| 65 uiDownAction: function(e) { | |
| 66 if (!this.noink) | |
| 67 this.downAction(e); | |
| 68 }, | |
| 69 | |
| 70 /** @param {Event=} e */ | |
| 71 downAction: function(e) { | |
| 72 if (this.ripples.length && this.holdDown) | |
| 73 return; | |
| 74 // TODO(dbeam): some things (i.e. paper-icon-button-light) dynamically | |
| 75 // create ripples on 'up', Ripples register an event listener on their | |
| 76 // parent (or shadow DOM host) when attached(). This sometimes causes | |
| 77 // duplicate events to fire on us. | |
| 78 this.debounce('show ripple', function() { this.__showRipple(e); }, 1); | |
| 79 }, | |
| 80 | |
| 81 /** | |
| 82 * @param {Event=} e | |
| 83 * @private | |
| 84 */ | |
| 85 __showRipple: function(e) { | |
| 86 var rect = this.getBoundingClientRect(); | |
| 87 | |
| 88 var roundedCenterX = function() { return Math.round(rect.width / 2); }; | |
| 89 var roundedCenterY = function() { return Math.round(rect.height / 2); }; | |
| 90 | |
| 91 var centered = !e || this.center; | |
| 92 if (centered) { | |
| 93 var x = roundedCenterX(); | |
| 94 var y = roundedCenterY(); | |
| 95 } else { | |
| 96 var sourceEvent = e.detail.sourceEvent; | |
| 97 var x = Math.round(sourceEvent.clientX - rect.left); | |
| 98 var y = Math.round(sourceEvent.clientY - rect.top); | |
| 24 } | 99 } |
| 25 | 100 |
| 26 ElementMetrics.prototype = { | 101 var corners = [ |
| 27 get boundingRect () { | 102 {x: 0, y: 0}, |
| 28 return this.element.getBoundingClientRect(); | 103 {x: rect.width, y: 0}, |
| 29 }, | 104 {x: 0, y: rect.height}, |
| 105 {x: rect.width, y: rect.height}, | |
| 106 ]; | |
| 30 | 107 |
| 31 furthestCornerDistanceFrom: function(x, y) { | 108 var cornerDistances = corners.map(function(corner) { |
| 32 var topLeft = Utility.distance(x, y, 0, 0); | 109 return Math.round(distance(x, y, corner.x, corner.y)); |
| 33 var topRight = Utility.distance(x, y, this.width, 0); | 110 }); |
| 34 var bottomLeft = Utility.distance(x, y, 0, this.height); | |
| 35 var bottomRight = Utility.distance(x, y, this.width, this.height); | |
| 36 | 111 |
| 37 return Math.max(topLeft, topRight, bottomLeft, bottomRight); | 112 var radius = Math.min(MAX_RADIUS, Math.max.apply(Math, cornerDistances)); |
| 38 } | |
| 39 }; | |
| 40 | 113 |
| 41 /** | 114 var startTranslate = (x - radius) + 'px, ' + (y - radius) + 'px'; |
| 42 * @param {HTMLElement} element | 115 if (this.recenters && !centered) { |
| 43 * @constructor | 116 var endTranslate = (roundedCenterX() - radius) + 'px, ' + |
| 44 */ | 117 (roundedCenterY() - radius) + 'px'; |
| 45 function Ripple(element) { | 118 } else { |
| 46 this.element = element; | 119 var endTranslate = startTranslate; |
| 47 this.color = window.getComputedStyle(element).color; | |
| 48 | |
| 49 this.wave = document.createElement('div'); | |
| 50 this.waveContainer = document.createElement('div'); | |
| 51 this.wave.style.backgroundColor = this.color; | |
| 52 this.wave.classList.add('wave'); | |
| 53 this.waveContainer.classList.add('wave-container'); | |
| 54 Polymer.dom(this.waveContainer).appendChild(this.wave); | |
| 55 | |
| 56 this.resetInteractionState(); | |
| 57 } | 120 } |
| 58 | 121 |
| 59 Ripple.MAX_RADIUS = 300; | 122 var ripple = document.createElement('div'); |
| 123 ripple.classList.add('ripple'); | |
| 124 ripple.style.height = ripple.style.width = (2 * radius) + 'px'; | |
| 60 | 125 |
| 61 Ripple.prototype = { | 126 this.ripples.push(ripple); |
| 62 get recenters() { | 127 this.shadowRoot.appendChild(ripple); |
| 63 return this.element.recenters; | |
| 64 }, | |
| 65 | 128 |
| 66 get center() { | 129 ripple.animate({ |
| 67 return this.element.center; | 130 // TODO(dbeam): scale to 90% of radius at .75 offset? |
| 68 }, | 131 transform: ['translate(' + startTranslate + ') scale(0)', |
| 132 'translate(' + endTranslate + ') scale(1)'], | |
| 133 }, { | |
| 134 duration: Math.max(MIN_DURATION, Math.log(radius) * radius), | |
| 135 easing: 'cubic-bezier(.2, .9, .1, .9)', | |
| 136 fill: 'forwards', | |
| 137 }); | |
| 138 }, | |
| 69 | 139 |
| 70 get mouseDownElapsed() { | 140 /** @param {Event=} e */ |
| 71 var elapsed; | 141 uiUpAction: function(e) { |
| 142 if (!this.noink) | |
| 143 this.upAction(); | |
| 144 }, | |
| 72 | 145 |
| 73 if (!this.mouseDownStart) { | 146 /** @param {Event=} e */ |
| 74 return 0; | 147 upAction: function(e) { |
| 75 } | 148 if (!this.holdDown) |
| 149 this.debounce('hide ripple', function() { this.__hideRipple(); }, 1); | |
| 150 }, | |
| 76 | 151 |
| 77 elapsed = Utility.now() - this.mouseDownStart; | 152 /** @private */ |
| 153 __hideRipple: function() { | |
| 154 this.ripples.forEach(function(ripple) { | |
| 155 var animation = ripple.animate({ | |
| 156 opacity: [getComputedStyle(ripple).opacity, 0], | |
|
dpapad
2017/02/22 21:07:26
Is it necessary to pass the current value? Will it
Dan Beam
2017/02/23 00:23:43
partial keyframes (only a "to" value) are not supp
| |
| 157 }, { | |
| 158 duration: 150, | |
| 159 fill: 'forwards', | |
| 160 }); | |
| 161 var removeRipple = function() { ripple.remove(); }; | |
| 162 animation.addEventListener('finish', removeRipple); | |
| 163 animation.addEventListener('cancel', removeRipple); | |
| 164 }); | |
| 165 this.ripples = []; | |
| 166 }, | |
| 78 | 167 |
| 79 if (this.mouseUpStart) { | 168 /** @protected */ |
| 80 elapsed -= this.mouseUpElapsed; | 169 _onEnterKeydown: function() { |
| 81 } | 170 this.uiDownAction(); |
| 171 this.async(this.uiUpAction, 1); | |
| 172 }, | |
| 82 | 173 |
| 83 return elapsed; | 174 /** @protected */ |
| 84 }, | 175 _onSpaceKeydown: function() { |
| 176 this.uiDownAction(); | |
| 177 }, | |
| 85 | 178 |
| 86 get mouseUpElapsed() { | 179 /** @protected */ |
| 87 return this.mouseUpStart ? | 180 _onSpaceKeyup: function() { |
| 88 Utility.now () - this.mouseUpStart : 0; | 181 this.uiUpAction(); |
| 89 }, | 182 }, |
| 90 | 183 |
| 91 get mouseDownElapsedSeconds() { | 184 /** @protected */ |
| 92 return this.mouseDownElapsed / 1000; | 185 _holdDownChanged: function(newHoldDown, oldHoldDown) { |
| 93 }, | 186 if (oldHoldDown === undefined) |
| 187 return; | |
| 188 if (newHoldDown) | |
| 189 this.downAction(); | |
| 190 else | |
| 191 this.upAction(); | |
| 192 }, | |
| 193 }); | |
| 94 | 194 |
| 95 get mouseUpElapsedSeconds() { | 195 })(); |
| 96 return this.mouseUpElapsed / 1000; | |
| 97 }, | |
| 98 | |
| 99 get mouseInteractionSeconds() { | |
| 100 return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds; | |
| 101 }, | |
| 102 | |
| 103 get initialOpacity() { | |
| 104 return this.element.initialOpacity; | |
| 105 }, | |
| 106 | |
| 107 get opacityDecayVelocity() { | |
| 108 return this.element.opacityDecayVelocity; | |
| 109 }, | |
| 110 | |
| 111 get radius() { | |
| 112 var width2 = this.containerMetrics.width * this.containerMetrics.width; | |
| 113 var height2 = this.containerMetrics.height * this.containerMetrics.heigh t; | |
| 114 var waveRadius = Math.min( | |
| 115 Math.sqrt(width2 + height2), | |
| 116 Ripple.MAX_RADIUS | |
| 117 ) * 1.1 + 5; | |
| 118 | |
| 119 var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS); | |
| 120 var timeNow = this.mouseInteractionSeconds / duration; | |
| 121 var size = waveRadius * (1 - Math.pow(80, -timeNow)); | |
| 122 | |
| 123 return Math.abs(size); | |
| 124 }, | |
| 125 | |
| 126 get opacity() { | |
| 127 if (!this.mouseUpStart) { | |
| 128 return this.initialOpacity; | |
| 129 } | |
| 130 | |
| 131 return Math.max( | |
| 132 0, | |
| 133 this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVe locity | |
| 134 ); | |
| 135 }, | |
| 136 | |
| 137 get outerOpacity() { | |
| 138 // Linear increase in background opacity, capped at the opacity | |
| 139 // of the wavefront (waveOpacity). | |
| 140 var outerOpacity = this.mouseUpElapsedSeconds * 0.3; | |
| 141 var waveOpacity = this.opacity; | |
| 142 | |
| 143 return Math.max( | |
| 144 0, | |
| 145 Math.min(outerOpacity, waveOpacity) | |
| 146 ); | |
| 147 }, | |
| 148 | |
| 149 get isOpacityFullyDecayed() { | |
| 150 return this.opacity < 0.01 && | |
| 151 this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS); | |
| 152 }, | |
| 153 | |
| 154 get isRestingAtMaxRadius() { | |
| 155 return this.opacity >= this.initialOpacity && | |
| 156 this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS); | |
| 157 }, | |
| 158 | |
| 159 get isAnimationComplete() { | |
| 160 return this.mouseUpStart ? | |
| 161 this.isOpacityFullyDecayed : this.isRestingAtMaxRadius; | |
| 162 }, | |
| 163 | |
| 164 get translationFraction() { | |
| 165 return Math.min( | |
| 166 1, | |
| 167 this.radius / this.containerMetrics.size * 2 / Math.sqrt(2) | |
| 168 ); | |
| 169 }, | |
| 170 | |
| 171 get xNow() { | |
| 172 if (this.xEnd) { | |
| 173 return this.xStart + this.translationFraction * (this.xEnd - this.xSta rt); | |
| 174 } | |
| 175 | |
| 176 return this.xStart; | |
| 177 }, | |
| 178 | |
| 179 get yNow() { | |
| 180 if (this.yEnd) { | |
| 181 return this.yStart + this.translationFraction * (this.yEnd - this.ySta rt); | |
| 182 } | |
| 183 | |
| 184 return this.yStart; | |
| 185 }, | |
| 186 | |
| 187 get isMouseDown() { | |
| 188 return this.mouseDownStart && !this.mouseUpStart; | |
| 189 }, | |
| 190 | |
| 191 resetInteractionState: function() { | |
| 192 this.maxRadius = 0; | |
| 193 this.mouseDownStart = 0; | |
| 194 this.mouseUpStart = 0; | |
| 195 | |
| 196 this.xStart = 0; | |
| 197 this.yStart = 0; | |
| 198 this.xEnd = 0; | |
| 199 this.yEnd = 0; | |
| 200 this.slideDistance = 0; | |
| 201 | |
| 202 this.containerMetrics = new ElementMetrics(this.element); | |
| 203 }, | |
| 204 | |
| 205 draw: function() { | |
| 206 var scale; | |
| 207 var translateString; | |
| 208 var dx; | |
| 209 var dy; | |
| 210 | |
| 211 this.wave.style.opacity = this.opacity; | |
| 212 | |
| 213 scale = this.radius / (this.containerMetrics.size / 2); | |
| 214 dx = this.xNow - (this.containerMetrics.width / 2); | |
| 215 dy = this.yNow - (this.containerMetrics.height / 2); | |
| 216 | |
| 217 | |
| 218 // 2d transform for safari because of border-radius and overflow:hidden clipping bug. | |
| 219 // https://bugs.webkit.org/show_bug.cgi?id=98538 | |
| 220 this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' + dy + 'px)'; | |
| 221 this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy + 'px, 0)'; | |
| 222 this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')'; | |
| 223 this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)'; | |
| 224 }, | |
| 225 | |
| 226 /** @param {Event=} event */ | |
| 227 downAction: function(event) { | |
| 228 var xCenter = this.containerMetrics.width / 2; | |
| 229 var yCenter = this.containerMetrics.height / 2; | |
| 230 | |
| 231 this.resetInteractionState(); | |
| 232 this.mouseDownStart = Utility.now(); | |
| 233 | |
| 234 if (this.center) { | |
| 235 this.xStart = xCenter; | |
| 236 this.yStart = yCenter; | |
| 237 this.slideDistance = Utility.distance( | |
| 238 this.xStart, this.yStart, this.xEnd, this.yEnd | |
| 239 ); | |
| 240 } else { | |
| 241 this.xStart = event ? | |
| 242 event.detail.x - this.containerMetrics.boundingRect.left : | |
| 243 this.containerMetrics.width / 2; | |
| 244 this.yStart = event ? | |
| 245 event.detail.y - this.containerMetrics.boundingRect.top : | |
| 246 this.containerMetrics.height / 2; | |
| 247 } | |
| 248 | |
| 249 if (this.recenters) { | |
| 250 this.xEnd = xCenter; | |
| 251 this.yEnd = yCenter; | |
| 252 this.slideDistance = Utility.distance( | |
| 253 this.xStart, this.yStart, this.xEnd, this.yEnd | |
| 254 ); | |
| 255 } | |
| 256 | |
| 257 this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom( | |
| 258 this.xStart, | |
| 259 this.yStart | |
| 260 ); | |
| 261 | |
| 262 this.waveContainer.style.top = | |
| 263 (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px' ; | |
| 264 this.waveContainer.style.left = | |
| 265 (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px'; | |
| 266 | |
| 267 this.waveContainer.style.width = this.containerMetrics.size + 'px'; | |
| 268 this.waveContainer.style.height = this.containerMetrics.size + 'px'; | |
| 269 }, | |
| 270 | |
| 271 /** @param {Event=} event */ | |
| 272 upAction: function(event) { | |
| 273 if (!this.isMouseDown) { | |
| 274 return; | |
| 275 } | |
| 276 | |
| 277 this.mouseUpStart = Utility.now(); | |
| 278 }, | |
| 279 | |
| 280 remove: function() { | |
| 281 Polymer.dom(this.waveContainer.parentNode).removeChild( | |
| 282 this.waveContainer | |
| 283 ); | |
| 284 } | |
| 285 }; | |
| 286 | |
| 287 Polymer({ | |
| 288 is: 'paper-ripple', | |
| 289 | |
| 290 behaviors: [ | |
| 291 Polymer.IronA11yKeysBehavior | |
| 292 ], | |
| 293 | |
| 294 properties: { | |
| 295 /** | |
| 296 * The initial opacity set on the wave. | |
| 297 * | |
| 298 * @attribute initialOpacity | |
| 299 * @type number | |
| 300 * @default 0.25 | |
| 301 */ | |
| 302 initialOpacity: { | |
| 303 type: Number, | |
| 304 value: 0.25 | |
| 305 }, | |
| 306 | |
| 307 /** | |
| 308 * How fast (opacity per second) the wave fades out. | |
| 309 * | |
| 310 * @attribute opacityDecayVelocity | |
| 311 * @type number | |
| 312 * @default 0.8 | |
| 313 */ | |
| 314 opacityDecayVelocity: { | |
| 315 type: Number, | |
| 316 value: 0.8 | |
| 317 }, | |
| 318 | |
| 319 /** | |
| 320 * If true, ripples will exhibit a gravitational pull towards | |
| 321 * the center of their container as they fade away. | |
| 322 * | |
| 323 * @attribute recenters | |
| 324 * @type boolean | |
| 325 * @default false | |
| 326 */ | |
| 327 recenters: { | |
| 328 type: Boolean, | |
| 329 value: false | |
| 330 }, | |
| 331 | |
| 332 /** | |
| 333 * If true, ripples will center inside its container | |
| 334 * | |
| 335 * @attribute recenters | |
| 336 * @type boolean | |
| 337 * @default false | |
| 338 */ | |
| 339 center: { | |
| 340 type: Boolean, | |
| 341 value: false | |
| 342 }, | |
| 343 | |
| 344 /** | |
| 345 * A list of the visual ripples. | |
| 346 * | |
| 347 * @attribute ripples | |
| 348 * @type Array | |
| 349 * @default [] | |
| 350 */ | |
| 351 ripples: { | |
| 352 type: Array, | |
| 353 value: function() { | |
| 354 return []; | |
| 355 } | |
| 356 }, | |
| 357 | |
| 358 /** | |
| 359 * True when there are visible ripples animating within the | |
| 360 * element. | |
| 361 */ | |
| 362 animating: { | |
| 363 type: Boolean, | |
| 364 readOnly: true, | |
| 365 reflectToAttribute: true, | |
| 366 value: false | |
| 367 }, | |
| 368 | |
| 369 /** | |
| 370 * If true, the ripple will remain in the "down" state until `holdDown` | |
| 371 * is set to false again. | |
| 372 */ | |
| 373 holdDown: { | |
| 374 type: Boolean, | |
| 375 value: false, | |
| 376 observer: '_holdDownChanged' | |
| 377 }, | |
| 378 | |
| 379 /** | |
| 380 * If true, the ripple will not generate a ripple effect | |
| 381 * via pointer interaction. | |
| 382 * Calling ripple's imperative api like `simulatedRipple` will | |
| 383 * still generate the ripple effect. | |
| 384 */ | |
| 385 noink: { | |
| 386 type: Boolean, | |
| 387 value: false | |
| 388 }, | |
| 389 | |
| 390 _animating: { | |
| 391 type: Boolean | |
| 392 }, | |
| 393 | |
| 394 _boundAnimate: { | |
| 395 type: Function, | |
| 396 value: function() { | |
| 397 return this.animate.bind(this); | |
| 398 } | |
| 399 } | |
| 400 }, | |
| 401 | |
| 402 get target () { | |
| 403 return this.keyEventTarget; | |
| 404 }, | |
| 405 | |
| 406 keyBindings: { | |
| 407 'enter:keydown': '_onEnterKeydown', | |
| 408 'space:keydown': '_onSpaceKeydown', | |
| 409 'space:keyup': '_onSpaceKeyup' | |
| 410 }, | |
| 411 | |
| 412 attached: function() { | |
| 413 // Set up a11yKeysBehavior to listen to key events on the target, | |
| 414 // so that space and enter activate the ripple even if the target doesn' t | |
| 415 // handle key events. The key handlers deal with `noink` themselves. | |
| 416 if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE | |
| 417 this.keyEventTarget = Polymer.dom(this).getOwnerRoot().host; | |
| 418 } else { | |
| 419 this.keyEventTarget = this.parentNode; | |
| 420 } | |
| 421 var keyEventTarget = /** @type {!EventTarget} */ (this.keyEventTarget); | |
| 422 this.listen(keyEventTarget, 'up', 'uiUpAction'); | |
| 423 this.listen(keyEventTarget, 'down', 'uiDownAction'); | |
| 424 }, | |
| 425 | |
| 426 detached: function() { | |
| 427 this.unlisten(this.keyEventTarget, 'up', 'uiUpAction'); | |
| 428 this.unlisten(this.keyEventTarget, 'down', 'uiDownAction'); | |
| 429 this.keyEventTarget = null; | |
| 430 }, | |
| 431 | |
| 432 get shouldKeepAnimating () { | |
| 433 for (var index = 0; index < this.ripples.length; ++index) { | |
| 434 if (!this.ripples[index].isAnimationComplete) { | |
| 435 return true; | |
| 436 } | |
| 437 } | |
| 438 | |
| 439 return false; | |
| 440 }, | |
| 441 | |
| 442 simulatedRipple: function() { | |
| 443 this.downAction(null); | |
| 444 | |
| 445 // Please see polymer/polymer#1305 | |
| 446 this.async(function() { | |
| 447 this.upAction(); | |
| 448 }, 1); | |
| 449 }, | |
| 450 | |
| 451 /** | |
| 452 * Provokes a ripple down effect via a UI event, | |
| 453 * respecting the `noink` property. | |
| 454 * @param {Event=} event | |
| 455 */ | |
| 456 uiDownAction: function(event) { | |
| 457 if (!this.noink) { | |
| 458 this.downAction(event); | |
| 459 } | |
| 460 }, | |
| 461 | |
| 462 /** | |
| 463 * Provokes a ripple down effect via a UI event, | |
| 464 * *not* respecting the `noink` property. | |
| 465 * @param {Event=} event | |
| 466 */ | |
| 467 downAction: function(event) { | |
| 468 if (this.holdDown && this.ripples.length > 0) { | |
| 469 return; | |
| 470 } | |
| 471 | |
| 472 var ripple = this.addRipple(); | |
| 473 | |
| 474 ripple.downAction(event); | |
| 475 | |
| 476 if (!this._animating) { | |
| 477 this._animating = true; | |
| 478 this.animate(); | |
| 479 } | |
| 480 }, | |
| 481 | |
| 482 /** | |
| 483 * Provokes a ripple up effect via a UI event, | |
| 484 * respecting the `noink` property. | |
| 485 * @param {Event=} event | |
| 486 */ | |
| 487 uiUpAction: function(event) { | |
| 488 if (!this.noink) { | |
| 489 this.upAction(event); | |
| 490 } | |
| 491 }, | |
| 492 | |
| 493 /** | |
| 494 * Provokes a ripple up effect via a UI event, | |
| 495 * *not* respecting the `noink` property. | |
| 496 * @param {Event=} event | |
| 497 */ | |
| 498 upAction: function(event) { | |
| 499 if (this.holdDown) { | |
| 500 return; | |
| 501 } | |
| 502 | |
| 503 this.ripples.forEach(function(ripple) { | |
| 504 ripple.upAction(event); | |
| 505 }); | |
| 506 | |
| 507 this._animating = true; | |
| 508 this.animate(); | |
| 509 }, | |
| 510 | |
| 511 onAnimationComplete: function() { | |
| 512 this._animating = false; | |
| 513 this.$.background.style.backgroundColor = null; | |
| 514 this.fire('transitionend'); | |
| 515 }, | |
| 516 | |
| 517 addRipple: function() { | |
| 518 var ripple = new Ripple(this); | |
| 519 | |
| 520 Polymer.dom(this.$.waves).appendChild(ripple.waveContainer); | |
| 521 this.$.background.style.backgroundColor = ripple.color; | |
| 522 this.ripples.push(ripple); | |
| 523 | |
| 524 this._setAnimating(true); | |
| 525 | |
| 526 return ripple; | |
| 527 }, | |
| 528 | |
| 529 removeRipple: function(ripple) { | |
| 530 var rippleIndex = this.ripples.indexOf(ripple); | |
| 531 | |
| 532 if (rippleIndex < 0) { | |
| 533 return; | |
| 534 } | |
| 535 | |
| 536 this.ripples.splice(rippleIndex, 1); | |
| 537 | |
| 538 ripple.remove(); | |
| 539 | |
| 540 if (!this.ripples.length) { | |
| 541 this._setAnimating(false); | |
| 542 } | |
| 543 }, | |
| 544 | |
| 545 /** | |
| 546 * This conflicts with Element#antimate(). | |
| 547 * https://developer.mozilla.org/en-US/docs/Web/API/Element/animate | |
| 548 * @suppress {checkTypes} | |
| 549 */ | |
| 550 animate: function() { | |
| 551 if (!this._animating) { | |
| 552 return; | |
| 553 } | |
| 554 var index; | |
| 555 var ripple; | |
| 556 | |
| 557 for (index = 0; index < this.ripples.length; ++index) { | |
| 558 ripple = this.ripples[index]; | |
| 559 | |
| 560 ripple.draw(); | |
| 561 | |
| 562 this.$.background.style.opacity = ripple.outerOpacity; | |
| 563 | |
| 564 if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) { | |
| 565 this.removeRipple(ripple); | |
| 566 } | |
| 567 } | |
| 568 | |
| 569 if (!this.shouldKeepAnimating && this.ripples.length === 0) { | |
| 570 this.onAnimationComplete(); | |
| 571 } else { | |
| 572 window.requestAnimationFrame(this._boundAnimate); | |
| 573 } | |
| 574 }, | |
| 575 | |
| 576 _onEnterKeydown: function() { | |
| 577 this.uiDownAction(); | |
| 578 this.async(this.uiUpAction, 1); | |
| 579 }, | |
| 580 | |
| 581 _onSpaceKeydown: function() { | |
| 582 this.uiDownAction(); | |
| 583 }, | |
| 584 | |
| 585 _onSpaceKeyup: function() { | |
| 586 this.uiUpAction(); | |
| 587 }, | |
| 588 | |
| 589 // note: holdDown does not respect noink since it can be a focus based | |
| 590 // effect. | |
| 591 _holdDownChanged: function(newVal, oldVal) { | |
| 592 if (oldVal === undefined) { | |
| 593 return; | |
| 594 } | |
| 595 if (newVal) { | |
| 596 this.downAction(); | |
| 597 } else { | |
| 598 this.upAction(); | |
| 599 } | |
| 600 } | |
| 601 | |
| 602 /** | |
| 603 Fired when the animation finishes. | |
| 604 This is useful if you want to wait until | |
| 605 the ripple animation finishes to perform some action. | |
| 606 | |
| 607 @event transitionend | |
| 608 @param {{node: Object}} detail Contains the animated node. | |
| 609 */ | |
| 610 }); | |
| 611 })(); | |
| OLD | NEW |