OLD | NEW |
(Empty) | |
| 1 |
| 2 |
| 3 (function() { |
| 4 |
| 5 var waveMaxRadius = 150; |
| 6 // |
| 7 // INK EQUATIONS |
| 8 // |
| 9 function waveRadiusFn(touchDownMs, touchUpMs, anim) { |
| 10 // Convert from ms to s. |
| 11 var touchDown = touchDownMs / 1000; |
| 12 var touchUp = touchUpMs / 1000; |
| 13 var totalElapsed = touchDown + touchUp; |
| 14 var ww = anim.width, hh = anim.height; |
| 15 // use diagonal size of container to avoid floating point math sadness |
| 16 var waveRadius = Math.min(Math.sqrt(ww * ww + hh * hh), waveMaxRadius) * 1
.1 + 5; |
| 17 var duration = 1.1 - .2 * (waveRadius / waveMaxRadius); |
| 18 var tt = (totalElapsed / duration); |
| 19 |
| 20 var size = waveRadius * (1 - Math.pow(80, -tt)); |
| 21 return Math.abs(size); |
| 22 } |
| 23 |
| 24 function waveOpacityFn(td, tu, anim) { |
| 25 // Convert from ms to s. |
| 26 var touchDown = td / 1000; |
| 27 var touchUp = tu / 1000; |
| 28 var totalElapsed = touchDown + touchUp; |
| 29 |
| 30 if (tu <= 0) { // before touch up |
| 31 return anim.initialOpacity; |
| 32 } |
| 33 return Math.max(0, anim.initialOpacity - touchUp * anim.opacityDecayVeloci
ty); |
| 34 } |
| 35 |
| 36 function waveOuterOpacityFn(td, tu, anim) { |
| 37 // Convert from ms to s. |
| 38 var touchDown = td / 1000; |
| 39 var touchUp = tu / 1000; |
| 40 |
| 41 // Linear increase in background opacity, capped at the opacity |
| 42 // of the wavefront (waveOpacity). |
| 43 var outerOpacity = touchDown * 0.3; |
| 44 var waveOpacity = waveOpacityFn(td, tu, anim); |
| 45 return Math.max(0, Math.min(outerOpacity, waveOpacity)); |
| 46 } |
| 47 |
| 48 // Determines whether the wave should be completely removed. |
| 49 function waveDidFinish(wave, radius, anim) { |
| 50 var waveOpacity = waveOpacityFn(wave.tDown, wave.tUp, anim); |
| 51 // If the wave opacity is 0 and the radius exceeds the bounds |
| 52 // of the element, then this is finished. |
| 53 if (waveOpacity < 0.01 && radius >= Math.min(wave.maxRadius, waveMaxRadius
)) { |
| 54 return true; |
| 55 } |
| 56 return false; |
| 57 }; |
| 58 |
| 59 function waveAtMaximum(wave, radius, anim) { |
| 60 var waveOpacity = waveOpacityFn(wave.tDown, wave.tUp, anim); |
| 61 if (waveOpacity >= anim.initialOpacity && radius >= Math.min(wave.maxRadiu
s, waveMaxRadius)) { |
| 62 return true; |
| 63 } |
| 64 return false; |
| 65 } |
| 66 |
| 67 // |
| 68 // DRAWING |
| 69 // |
| 70 function drawRipple(ctx, x, y, radius, innerColor, outerColor) { |
| 71 if (outerColor) { |
| 72 ctx.fillStyle = outerColor; |
| 73 ctx.fillRect(0,0,ctx.canvas.width, ctx.canvas.height); |
| 74 } |
| 75 ctx.beginPath(); |
| 76 ctx.arc(x, y, radius, 0, 2 * Math.PI, false); |
| 77 ctx.fillStyle = innerColor; |
| 78 ctx.fill(); |
| 79 } |
| 80 |
| 81 // |
| 82 // SETUP |
| 83 // |
| 84 function createWave(elem) { |
| 85 var elementStyle = window.getComputedStyle(elem); |
| 86 var fgColor = elementStyle.color; |
| 87 |
| 88 var wave = { |
| 89 waveColor: fgColor, |
| 90 maxRadius: 0, |
| 91 isMouseDown: false, |
| 92 mouseDownStart: 0.0, |
| 93 mouseUpStart: 0.0, |
| 94 tDown: 0, |
| 95 tUp: 0 |
| 96 }; |
| 97 return wave; |
| 98 } |
| 99 |
| 100 function removeWaveFromScope(scope, wave) { |
| 101 if (scope.waves) { |
| 102 var pos = scope.waves.indexOf(wave); |
| 103 scope.waves.splice(pos, 1); |
| 104 } |
| 105 }; |
| 106 |
| 107 // Shortcuts. |
| 108 var pow = Math.pow; |
| 109 var now = Date.now; |
| 110 if (window.performance && performance.now) { |
| 111 now = performance.now.bind(performance); |
| 112 } |
| 113 |
| 114 function cssColorWithAlpha(cssColor, alpha) { |
| 115 var parts = cssColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); |
| 116 if (typeof alpha == 'undefined') { |
| 117 alpha = 1; |
| 118 } |
| 119 if (!parts) { |
| 120 return 'rgba(255, 255, 255, ' + alpha + ')'; |
| 121 } |
| 122 return 'rgba(' + parts[1] + ', ' + parts[2] + ', ' + parts[3] + ', ' + a
lpha + ')'; |
| 123 } |
| 124 |
| 125 function dist(p1, p2) { |
| 126 return Math.sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2)); |
| 127 } |
| 128 |
| 129 function distanceFromPointToFurthestCorner(point, size) { |
| 130 var tl_d = dist(point, {x: 0, y: 0}); |
| 131 var tr_d = dist(point, {x: size.w, y: 0}); |
| 132 var bl_d = dist(point, {x: 0, y: size.h}); |
| 133 var br_d = dist(point, {x: size.w, y: size.h}); |
| 134 return Math.max(tl_d, tr_d, bl_d, br_d); |
| 135 } |
| 136 |
| 137 Polymer('paper-ripple', { |
| 138 |
| 139 /** |
| 140 * The initial opacity set on the wave. |
| 141 * |
| 142 * @attribute initialOpacity |
| 143 * @type number |
| 144 * @default 0.25 |
| 145 */ |
| 146 initialOpacity: 0.25, |
| 147 |
| 148 /** |
| 149 * How fast (opacity per second) the wave fades out. |
| 150 * |
| 151 * @attribute opacityDecayVelocity |
| 152 * @type number |
| 153 * @default 0.8 |
| 154 */ |
| 155 opacityDecayVelocity: 0.8, |
| 156 |
| 157 backgroundFill: true, |
| 158 pixelDensity: 2, |
| 159 |
| 160 eventDelegates: { |
| 161 down: 'downAction', |
| 162 up: 'upAction' |
| 163 }, |
| 164 |
| 165 attached: function() { |
| 166 // create the canvas element manually becase ios |
| 167 // does not render the canvas element if it is not created in the |
| 168 // main document (component templates are created in a |
| 169 // different document). See: |
| 170 // https://bugs.webkit.org/show_bug.cgi?id=109073. |
| 171 if (!this.$.canvas) { |
| 172 var canvas = document.createElement('canvas'); |
| 173 canvas.id = 'canvas'; |
| 174 this.shadowRoot.appendChild(canvas); |
| 175 this.$.canvas = canvas; |
| 176 } |
| 177 }, |
| 178 |
| 179 ready: function() { |
| 180 this.waves = []; |
| 181 }, |
| 182 |
| 183 setupCanvas: function() { |
| 184 this.$.canvas.setAttribute('width', this.$.canvas.clientWidth * this.pix
elDensity + "px"); |
| 185 this.$.canvas.setAttribute('height', this.$.canvas.clientHeight * this.p
ixelDensity + "px"); |
| 186 var ctx = this.$.canvas.getContext('2d'); |
| 187 ctx.scale(this.pixelDensity, this.pixelDensity); |
| 188 if (!this._loop) { |
| 189 this._loop = this.animate.bind(this, ctx); |
| 190 } |
| 191 }, |
| 192 |
| 193 downAction: function(e) { |
| 194 this.setupCanvas(); |
| 195 var wave = createWave(this.$.canvas); |
| 196 |
| 197 this.cancelled = false; |
| 198 wave.isMouseDown = true; |
| 199 wave.tDown = 0.0; |
| 200 wave.tUp = 0.0; |
| 201 wave.mouseUpStart = 0.0; |
| 202 wave.mouseDownStart = now(); |
| 203 |
| 204 var width = this.$.canvas.width / 2; // Retina canvas |
| 205 var height = this.$.canvas.height / 2; |
| 206 var rect = this.getBoundingClientRect(); |
| 207 var touchX = e.x - rect.left; |
| 208 var touchY = e.y - rect.top; |
| 209 |
| 210 wave.startPosition = {x:touchX, y:touchY}; |
| 211 |
| 212 if (this.classList.contains("recenteringTouch")) { |
| 213 wave.endPosition = {x: width / 2, y: height / 2}; |
| 214 wave.slideDistance = dist(wave.startPosition, wave.endPosition); |
| 215 } |
| 216 wave.containerSize = Math.max(width, height); |
| 217 wave.maxRadius = distanceFromPointToFurthestCorner(wave.startPosition, {
w: width, h: height}); |
| 218 this.waves.push(wave); |
| 219 requestAnimationFrame(this._loop); |
| 220 }, |
| 221 |
| 222 upAction: function() { |
| 223 for (var i = 0; i < this.waves.length; i++) { |
| 224 // Declare the next wave that has mouse down to be mouse'ed up. |
| 225 var wave = this.waves[i]; |
| 226 if (wave.isMouseDown) { |
| 227 wave.isMouseDown = false |
| 228 wave.mouseUpStart = now(); |
| 229 wave.mouseDownStart = 0; |
| 230 wave.tUp = 0.0; |
| 231 break; |
| 232 } |
| 233 } |
| 234 this._loop && requestAnimationFrame(this._loop); |
| 235 }, |
| 236 |
| 237 cancel: function() { |
| 238 this.cancelled = true; |
| 239 }, |
| 240 |
| 241 animate: function(ctx) { |
| 242 var shouldRenderNextFrame = false; |
| 243 |
| 244 // Clear the canvas |
| 245 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); |
| 246 |
| 247 var deleteTheseWaves = []; |
| 248 // The oldest wave's touch down duration |
| 249 var longestTouchDownDuration = 0; |
| 250 var longestTouchUpDuration = 0; |
| 251 // Save the last known wave color |
| 252 var lastWaveColor = null; |
| 253 // wave animation values |
| 254 var anim = { |
| 255 initialOpacity: this.initialOpacity, |
| 256 opacityDecayVelocity: this.opacityDecayVelocity, |
| 257 height: ctx.canvas.height, |
| 258 width: ctx.canvas.width |
| 259 } |
| 260 |
| 261 for (var i = 0; i < this.waves.length; i++) { |
| 262 var wave = this.waves[i]; |
| 263 |
| 264 if (wave.mouseDownStart > 0) { |
| 265 wave.tDown = now() - wave.mouseDownStart; |
| 266 } |
| 267 if (wave.mouseUpStart > 0) { |
| 268 wave.tUp = now() - wave.mouseUpStart; |
| 269 } |
| 270 |
| 271 // Determine how long the touch has been up or down. |
| 272 var tUp = wave.tUp; |
| 273 var tDown = wave.tDown; |
| 274 longestTouchDownDuration = Math.max(longestTouchDownDuration, tDown); |
| 275 longestTouchUpDuration = Math.max(longestTouchUpDuration, tUp); |
| 276 |
| 277 // Obtain the instantenous size and alpha of the ripple. |
| 278 var radius = waveRadiusFn(tDown, tUp, anim); |
| 279 var waveAlpha = waveOpacityFn(tDown, tUp, anim); |
| 280 var waveColor = cssColorWithAlpha(wave.waveColor, waveAlpha); |
| 281 lastWaveColor = wave.waveColor; |
| 282 |
| 283 // Position of the ripple. |
| 284 var x = wave.startPosition.x; |
| 285 var y = wave.startPosition.y; |
| 286 |
| 287 // Ripple gravitational pull to the center of the canvas. |
| 288 if (wave.endPosition) { |
| 289 |
| 290 // This translates from the origin to the center of the view based
on the max dimension of |
| 291 var translateFraction = Math.min(1, radius / wave.containerSize * 2
/ Math.sqrt(2) ); |
| 292 |
| 293 x += translateFraction * (wave.endPosition.x - wave.startPosition.x)
; |
| 294 y += translateFraction * (wave.endPosition.y - wave.startPosition.y)
; |
| 295 } |
| 296 |
| 297 // If we do a background fill fade too, work out the correct color. |
| 298 var bgFillColor = null; |
| 299 if (this.backgroundFill) { |
| 300 var bgFillAlpha = waveOuterOpacityFn(tDown, tUp, anim); |
| 301 bgFillColor = cssColorWithAlpha(wave.waveColor, bgFillAlpha); |
| 302 } |
| 303 |
| 304 // Draw the ripple. |
| 305 drawRipple(ctx, x, y, radius, waveColor, bgFillColor); |
| 306 |
| 307 // Determine whether there is any more rendering to be done. |
| 308 var maximumWave = waveAtMaximum(wave, radius, anim); |
| 309 var waveDissipated = waveDidFinish(wave, radius, anim); |
| 310 var shouldKeepWave = !waveDissipated || maximumWave; |
| 311 var shouldRenderWaveAgain = !waveDissipated && !maximumWave; |
| 312 shouldRenderNextFrame = shouldRenderNextFrame || shouldRenderWaveAgain
; |
| 313 if (!shouldKeepWave || this.cancelled) { |
| 314 deleteTheseWaves.push(wave); |
| 315 } |
| 316 } |
| 317 |
| 318 if (shouldRenderNextFrame) { |
| 319 requestAnimationFrame(this._loop); |
| 320 } |
| 321 |
| 322 for (var i = 0; i < deleteTheseWaves.length; ++i) { |
| 323 var wave = deleteTheseWaves[i]; |
| 324 removeWaveFromScope(this, wave); |
| 325 } |
| 326 |
| 327 if (!this.waves.length) { |
| 328 // If there is nothing to draw, clear any drawn waves now because |
| 329 // we're not going to get another requestAnimationFrame any more. |
| 330 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); |
| 331 this._loop = null; |
| 332 } |
| 333 } |
| 334 |
| 335 }); |
| 336 |
| 337 })(); |
| 338 |
OLD | NEW |