| OLD | NEW |
| 1 (function() { | 1 (function() { |
| 2 var Utility = { | 2 |
| 3 distance: function(x1, y1, x2, y2) { | 3 var MAX_RADIUS_PX = 300; |
| 4 var xDelta = (x1 - x2); | 4 var MIN_DURATION_MS = 800; |
| 5 var yDelta = (y1 - y2); | 5 |
| 6 | 6 /** |
| 7 return Math.sqrt(xDelta * xDelta + yDelta * yDelta); | 7 * @param {number} x1 |
| 8 }, | 8 * @param {number} y1 |
| 9 | 9 * @param {number} x2 |
| 10 now: window.performance && window.performance.now ? | 10 * @param {number} y2 |
| 11 window.performance.now.bind(window.performance) : Date.now | 11 * @return {number} The distance between (x1, y1) and (x2, y2). |
| 12 }; | 12 */ |
| 13 | 13 var distance = function(x1, y1, x2, y2) { |
| 14 /** | 14 var xDelta = x1 - x2; |
| 15 * @param {HTMLElement} element | 15 var yDelta = y1 - y2; |
| 16 * @constructor | 16 return Math.sqrt(xDelta * xDelta + yDelta * yDelta); |
| 17 */ | 17 }; |
| 18 function ElementMetrics(element) { | 18 |
| 19 this.element = element; | 19 Polymer({ |
| 20 this.width = this.boundingRect.width; | 20 is: 'paper-ripple', |
| 21 this.height = this.boundingRect.height; | 21 |
| 22 | 22 behaviors: [Polymer.IronA11yKeysBehavior], |
| 23 this.size = Math.max(this.width, this.height); | 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.keyEventTarget = /** @type {!EventTarget} */ (this.keyEventTarget); |
| 48 this.listen(this.keyEventTarget, 'up', 'uiUpAction'); |
| 49 this.listen(this.keyEventTarget, 'down', 'uiDownAction'); |
| 50 }, |
| 51 |
| 52 /** @override */ |
| 53 detached: function() { |
| 54 this.unlisten(this.keyEventTarget, 'up', 'uiUpAction'); |
| 55 this.unlisten(this.keyEventTarget, 'down', 'uiDownAction'); |
| 56 this.keyEventTarget = null; |
| 57 }, |
| 58 |
| 59 simulatedRipple: function() { |
| 60 this.downAction(); |
| 61 // Using a 1ms delay ensures a macro-task. |
| 62 this.async(function() { this.upAction(); }.bind(this), 1); |
| 63 }, |
| 64 |
| 65 /** @param {Event=} e */ |
| 66 uiDownAction: function(e) { |
| 67 if (!this.noink) |
| 68 this.downAction(e); |
| 69 }, |
| 70 |
| 71 /** @param {Event=} e */ |
| 72 downAction: function(e) { |
| 73 if (this.ripples.length && this.holdDown) |
| 74 return; |
| 75 // TODO(dbeam): some things (i.e. paper-icon-button-light) dynamically |
| 76 // create ripples on 'up', Ripples register an event listener on their |
| 77 // parent (or shadow DOM host) when attached(). This sometimes causes |
| 78 // duplicate events to fire on us. |
| 79 this.debounce('show ripple', function() { this.__showRipple(e); }, 1); |
| 80 }, |
| 81 |
| 82 /** |
| 83 * @param {Event=} e |
| 84 * @private |
| 85 * @suppress {checkTypes} |
| 86 */ |
| 87 __showRipple: function(e) { |
| 88 var rect = this.getBoundingClientRect(); |
| 89 |
| 90 var roundedCenterX = function() { return Math.round(rect.width / 2); }; |
| 91 var roundedCenterY = function() { return Math.round(rect.height / 2); }; |
| 92 |
| 93 var centered = !e || this.center; |
| 94 if (centered) { |
| 95 var x = roundedCenterX(); |
| 96 var y = roundedCenterY(); |
| 97 } else { |
| 98 var sourceEvent = e.detail.sourceEvent; |
| 99 var x = Math.round(sourceEvent.clientX - rect.left); |
| 100 var y = Math.round(sourceEvent.clientY - rect.top); |
| 24 } | 101 } |
| 25 | 102 |
| 26 ElementMetrics.prototype = { | 103 var corners = [ |
| 27 get boundingRect () { | 104 {x: 0, y: 0}, |
| 28 return this.element.getBoundingClientRect(); | 105 {x: rect.width, y: 0}, |
| 29 }, | 106 {x: 0, y: rect.height}, |
| 30 | 107 {x: rect.width, y: rect.height}, |
| 31 furthestCornerDistanceFrom: function(x, y) { | 108 ]; |
| 32 var topLeft = Utility.distance(x, y, 0, 0); | 109 |
| 33 var topRight = Utility.distance(x, y, this.width, 0); | 110 var cornerDistances = corners.map(function(corner) { |
| 34 var bottomLeft = Utility.distance(x, y, 0, this.height); | 111 return Math.round(distance(x, y, corner.x, corner.y)); |
| 35 var bottomRight = Utility.distance(x, y, this.width, this.height); | 112 }); |
| 36 | 113 |
| 37 return Math.max(topLeft, topRight, bottomLeft, bottomRight); | 114 var radius = Math.min(MAX_RADIUS_PX, Math.max.apply(Math, cornerDistances)); |
| 115 |
| 116 var startTranslate = (x - radius) + 'px, ' + (y - radius) + 'px'; |
| 117 if (this.recenters && !centered) { |
| 118 var endTranslate = (roundedCenterX() - radius) + 'px, ' + |
| 119 (roundedCenterY() - radius) + 'px'; |
| 120 } else { |
| 121 var endTranslate = startTranslate; |
| 122 } |
| 123 |
| 124 var ripple = document.createElement('div'); |
| 125 ripple.classList.add('ripple'); |
| 126 ripple.style.height = ripple.style.width = (2 * radius) + 'px'; |
| 127 |
| 128 this.ripples.push(ripple); |
| 129 this.shadowRoot.appendChild(ripple); |
| 130 |
| 131 ripple.animate({ |
| 132 // TODO(dbeam): scale to 90% of radius at .75 offset? |
| 133 transform: ['translate(' + startTranslate + ') scale(0)', |
| 134 'translate(' + endTranslate + ') scale(1)'], |
| 135 }, { |
| 136 duration: Math.max(MIN_DURATION_MS, Math.log(radius) * radius) || 0, |
| 137 easing: 'cubic-bezier(.2, .9, .1, .9)', |
| 138 fill: 'forwards', |
| 139 }); |
| 140 }, |
| 141 |
| 142 /** @param {Event=} e */ |
| 143 uiUpAction: function(e) { |
| 144 if (!this.noink) |
| 145 this.upAction(); |
| 146 }, |
| 147 |
| 148 /** @param {Event=} e */ |
| 149 upAction: function(e) { |
| 150 if (!this.holdDown) |
| 151 this.debounce('hide ripple', function() { this.__hideRipple(); }, 1); |
| 152 }, |
| 153 |
| 154 /** |
| 155 * @private |
| 156 * @suppress {checkTypes} |
| 157 */ |
| 158 __hideRipple: function() { |
| 159 this.ripples.forEach(function(ripple) { |
| 160 var removeRipple = function() { ripple.remove(); }; |
| 161 // TODO(dbeam): should we be firing a transitionend event here? Does |
| 162 // anybody actually use it? It's in the original paper-ripple. |
| 163 var opacity = getComputedStyle(ripple).opacity; |
| 164 if (!opacity.length) { |
| 165 removeRipple(); |
| 166 } else { |
| 167 var animation = ripple.animate({ |
| 168 opacity: [opacity, 0], |
| 169 }, { |
| 170 duration: 150, |
| 171 fill: 'forwards', |
| 172 }); |
| 173 animation.addEventListener('finish', removeRipple); |
| 174 animation.addEventListener('cancel', removeRipple); |
| 38 } | 175 } |
| 39 }; | |
| 40 | |
| 41 /** | |
| 42 * @param {HTMLElement} element | |
| 43 * @constructor | |
| 44 */ | |
| 45 function Ripple(element) { | |
| 46 this.element = element; | |
| 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 } | |
| 58 | |
| 59 Ripple.MAX_RADIUS = 300; | |
| 60 | |
| 61 Ripple.prototype = { | |
| 62 get recenters() { | |
| 63 return this.element.recenters; | |
| 64 }, | |
| 65 | |
| 66 get center() { | |
| 67 return this.element.center; | |
| 68 }, | |
| 69 | |
| 70 get mouseDownElapsed() { | |
| 71 var elapsed; | |
| 72 | |
| 73 if (!this.mouseDownStart) { | |
| 74 return 0; | |
| 75 } | |
| 76 | |
| 77 elapsed = Utility.now() - this.mouseDownStart; | |
| 78 | |
| 79 if (this.mouseUpStart) { | |
| 80 elapsed -= this.mouseUpElapsed; | |
| 81 } | |
| 82 | |
| 83 return elapsed; | |
| 84 }, | |
| 85 | |
| 86 get mouseUpElapsed() { | |
| 87 return this.mouseUpStart ? | |
| 88 Utility.now () - this.mouseUpStart : 0; | |
| 89 }, | |
| 90 | |
| 91 get mouseDownElapsedSeconds() { | |
| 92 return this.mouseDownElapsed / 1000; | |
| 93 }, | |
| 94 | |
| 95 get mouseUpElapsedSeconds() { | |
| 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 }); | 176 }); |
| 611 })(); | 177 this.ripples = []; |
| 178 }, |
| 179 |
| 180 /** @protected */ |
| 181 _onEnterKeydown: function() { |
| 182 this.uiDownAction(); |
| 183 this.async(this.uiUpAction, 1); |
| 184 }, |
| 185 |
| 186 /** @protected */ |
| 187 _onSpaceKeydown: function() { |
| 188 this.uiDownAction(); |
| 189 }, |
| 190 |
| 191 /** @protected */ |
| 192 _onSpaceKeyup: function() { |
| 193 this.uiUpAction(); |
| 194 }, |
| 195 |
| 196 /** @protected */ |
| 197 _holdDownChanged: function(newHoldDown, oldHoldDown) { |
| 198 if (oldHoldDown === undefined) |
| 199 return; |
| 200 if (newHoldDown) |
| 201 this.downAction(); |
| 202 else |
| 203 this.upAction(); |
| 204 }, |
| 205 }); |
| 206 |
| 207 })(); |
| OLD | NEW |