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 | |
52 // If the wave opacity is 0 and the radius exceeds the bounds | |
53 // of the element, then this is finished. | |
54 return waveOpacity < 0.01 && radius >= Math.min(wave.maxRadius, waveMaxRad
ius); | |
55 }; | |
56 | |
57 function waveAtMaximum(wave, radius, anim) { | |
58 var waveOpacity = waveOpacityFn(wave.tDown, wave.tUp, anim); | |
59 | |
60 return waveOpacity >= anim.initialOpacity && radius >= Math.min(wave.maxRa
dius, waveMaxRadius); | |
61 } | |
62 | |
63 // | |
64 // DRAWING | |
65 // | |
66 function drawRipple(ctx, x, y, radius, innerAlpha, outerAlpha) { | |
67 // Only animate opacity and transform | |
68 if (outerAlpha !== undefined) { | |
69 ctx.bg.style.opacity = outerAlpha; | |
70 } | |
71 ctx.wave.style.opacity = innerAlpha; | |
72 | |
73 var s = radius / (ctx.containerSize / 2); | |
74 var dx = x - (ctx.containerWidth / 2); | |
75 var dy = y - (ctx.containerHeight / 2); | |
76 | |
77 ctx.wc.style.webkitTransform = 'translate3d(' + dx + 'px,' + dy + 'px,0)'; | |
78 ctx.wc.style.transform = 'translate3d(' + dx + 'px,' + dy + 'px,0)'; | |
79 | |
80 // 2d transform for safari because of border-radius and overflow:hidden cl
ipping bug. | |
81 // https://bugs.webkit.org/show_bug.cgi?id=98538 | |
82 ctx.wave.style.webkitTransform = 'scale(' + s + ',' + s + ')'; | |
83 ctx.wave.style.transform = 'scale3d(' + s + ',' + s + ',1)'; | |
84 } | |
85 | |
86 // | |
87 // SETUP | |
88 // | |
89 function createWave(elem) { | |
90 var elementStyle = window.getComputedStyle(elem); | |
91 var fgColor = elementStyle.color; | |
92 | |
93 var inner = document.createElement('div'); | |
94 inner.style.backgroundColor = fgColor; | |
95 inner.classList.add('wave'); | |
96 | |
97 var outer = document.createElement('div'); | |
98 outer.classList.add('wave-container'); | |
99 outer.appendChild(inner); | |
100 | |
101 var container = elem.$.waves; | |
102 container.appendChild(outer); | |
103 | |
104 elem.$.bg.style.backgroundColor = fgColor; | |
105 | |
106 var wave = { | |
107 bg: elem.$.bg, | |
108 wc: outer, | |
109 wave: inner, | |
110 waveColor: fgColor, | |
111 maxRadius: 0, | |
112 isMouseDown: false, | |
113 mouseDownStart: 0.0, | |
114 mouseUpStart: 0.0, | |
115 tDown: 0, | |
116 tUp: 0 | |
117 }; | |
118 return wave; | |
119 } | |
120 | |
121 function removeWaveFromScope(scope, wave) { | |
122 if (scope.waves) { | |
123 var pos = scope.waves.indexOf(wave); | |
124 scope.waves.splice(pos, 1); | |
125 // FIXME cache nodes | |
126 wave.wc.remove(); | |
127 } | |
128 }; | |
129 | |
130 // Shortcuts. | |
131 var pow = Math.pow; | |
132 var now = Date.now; | |
133 if (window.performance && performance.now) { | |
134 now = performance.now.bind(performance); | |
135 } | |
136 | |
137 function cssColorWithAlpha(cssColor, alpha) { | |
138 var parts = cssColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); | |
139 if (typeof alpha == 'undefined') { | |
140 alpha = 1; | |
141 } | |
142 if (!parts) { | |
143 return 'rgba(255, 255, 255, ' + alpha + ')'; | |
144 } | |
145 return 'rgba(' + parts[1] + ', ' + parts[2] + ', ' + parts[3] + ', ' + a
lpha + ')'; | |
146 } | |
147 | |
148 function dist(p1, p2) { | |
149 return Math.sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2)); | |
150 } | |
151 | |
152 function distanceFromPointToFurthestCorner(point, size) { | |
153 var tl_d = dist(point, {x: 0, y: 0}); | |
154 var tr_d = dist(point, {x: size.w, y: 0}); | |
155 var bl_d = dist(point, {x: 0, y: size.h}); | |
156 var br_d = dist(point, {x: size.w, y: size.h}); | |
157 return Math.max(tl_d, tr_d, bl_d, br_d); | |
158 } | |
159 | |
160 Polymer('paper-ripple', { | |
161 | |
162 /** | |
163 * The initial opacity set on the wave. | |
164 * | |
165 * @attribute initialOpacity | |
166 * @type number | |
167 * @default 0.25 | |
168 */ | |
169 initialOpacity: 0.25, | |
170 | |
171 /** | |
172 * How fast (opacity per second) the wave fades out. | |
173 * | |
174 * @attribute opacityDecayVelocity | |
175 * @type number | |
176 * @default 0.8 | |
177 */ | |
178 opacityDecayVelocity: 0.8, | |
179 | |
180 backgroundFill: true, | |
181 pixelDensity: 2, | |
182 | |
183 eventDelegates: { | |
184 down: 'downAction', | |
185 up: 'upAction' | |
186 }, | |
187 | |
188 ready: function() { | |
189 this.waves = []; | |
190 }, | |
191 | |
192 downAction: function(e) { | |
193 var wave = createWave(this); | |
194 | |
195 this.cancelled = false; | |
196 wave.isMouseDown = true; | |
197 wave.tDown = 0.0; | |
198 wave.tUp = 0.0; | |
199 wave.mouseUpStart = 0.0; | |
200 wave.mouseDownStart = now(); | |
201 | |
202 var rect = this.getBoundingClientRect(); | |
203 var width = rect.width; | |
204 var height = rect.height; | |
205 var touchX = e.x - rect.left; | |
206 var touchY = e.y - rect.top; | |
207 | |
208 wave.startPosition = {x:touchX, y:touchY}; | |
209 | |
210 if (this.classList.contains("recenteringTouch")) { | |
211 wave.endPosition = {x: width / 2, y: height / 2}; | |
212 wave.slideDistance = dist(wave.startPosition, wave.endPosition); | |
213 } | |
214 wave.containerSize = Math.max(width, height); | |
215 wave.containerWidth = width; | |
216 wave.containerHeight = height; | |
217 wave.maxRadius = distanceFromPointToFurthestCorner(wave.startPosition, {
w: width, h: height}); | |
218 | |
219 // The wave is circular so constrain its container to 1:1 | |
220 wave.wc.style.top = (wave.containerHeight - wave.containerSize) / 2 + 'p
x'; | |
221 wave.wc.style.left = (wave.containerWidth - wave.containerSize) / 2 + 'p
x'; | |
222 wave.wc.style.width = wave.containerSize + 'px'; | |
223 wave.wc.style.height = wave.containerSize + 'px'; | |
224 | |
225 this.waves.push(wave); | |
226 | |
227 if (!this._loop) { | |
228 this._loop = this.animate.bind(this, { | |
229 width: width, | |
230 height: height | |
231 }); | |
232 requestAnimationFrame(this._loop); | |
233 } | |
234 // else there is already a rAF | |
235 }, | |
236 | |
237 upAction: function() { | |
238 for (var i = 0; i < this.waves.length; i++) { | |
239 // Declare the next wave that has mouse down to be mouse'ed up. | |
240 var wave = this.waves[i]; | |
241 if (wave.isMouseDown) { | |
242 wave.isMouseDown = false | |
243 wave.mouseUpStart = now(); | |
244 wave.mouseDownStart = 0; | |
245 wave.tUp = 0.0; | |
246 break; | |
247 } | |
248 } | |
249 this._loop && requestAnimationFrame(this._loop); | |
250 }, | |
251 | |
252 cancel: function() { | |
253 this.cancelled = true; | |
254 }, | |
255 | |
256 animate: function(ctx) { | |
257 var shouldRenderNextFrame = false; | |
258 | |
259 var deleteTheseWaves = []; | |
260 // The oldest wave's touch down duration | |
261 var longestTouchDownDuration = 0; | |
262 var longestTouchUpDuration = 0; | |
263 // Save the last known wave color | |
264 var lastWaveColor = null; | |
265 // wave animation values | |
266 var anim = { | |
267 initialOpacity: this.initialOpacity, | |
268 opacityDecayVelocity: this.opacityDecayVelocity, | |
269 height: ctx.height, | |
270 width: ctx.width | |
271 } | |
272 | |
273 for (var i = 0; i < this.waves.length; i++) { | |
274 var wave = this.waves[i]; | |
275 | |
276 if (wave.mouseDownStart > 0) { | |
277 wave.tDown = now() - wave.mouseDownStart; | |
278 } | |
279 if (wave.mouseUpStart > 0) { | |
280 wave.tUp = now() - wave.mouseUpStart; | |
281 } | |
282 | |
283 // Determine how long the touch has been up or down. | |
284 var tUp = wave.tUp; | |
285 var tDown = wave.tDown; | |
286 longestTouchDownDuration = Math.max(longestTouchDownDuration, tDown); | |
287 longestTouchUpDuration = Math.max(longestTouchUpDuration, tUp); | |
288 | |
289 // Obtain the instantenous size and alpha of the ripple. | |
290 var radius = waveRadiusFn(tDown, tUp, anim); | |
291 var waveAlpha = waveOpacityFn(tDown, tUp, anim); | |
292 var waveColor = cssColorWithAlpha(wave.waveColor, waveAlpha); | |
293 lastWaveColor = wave.waveColor; | |
294 | |
295 // Position of the ripple. | |
296 var x = wave.startPosition.x; | |
297 var y = wave.startPosition.y; | |
298 | |
299 // Ripple gravitational pull to the center of the canvas. | |
300 if (wave.endPosition) { | |
301 | |
302 // This translates from the origin to the center of the view based
on the max dimension of | |
303 var translateFraction = Math.min(1, radius / wave.containerSize * 2
/ Math.sqrt(2) ); | |
304 | |
305 x += translateFraction * (wave.endPosition.x - wave.startPosition.x)
; | |
306 y += translateFraction * (wave.endPosition.y - wave.startPosition.y)
; | |
307 } | |
308 | |
309 // If we do a background fill fade too, work out the correct color. | |
310 var bgFillColor = null; | |
311 if (this.backgroundFill) { | |
312 var bgFillAlpha = waveOuterOpacityFn(tDown, tUp, anim); | |
313 bgFillColor = cssColorWithAlpha(wave.waveColor, bgFillAlpha); | |
314 } | |
315 | |
316 // Draw the ripple. | |
317 drawRipple(wave, x, y, radius, waveAlpha, bgFillAlpha); | |
318 | |
319 // Determine whether there is any more rendering to be done. | |
320 var maximumWave = waveAtMaximum(wave, radius, anim); | |
321 var waveDissipated = waveDidFinish(wave, radius, anim); | |
322 var shouldKeepWave = !waveDissipated || maximumWave; | |
323 // keep rendering dissipating wave when at maximum radius on upAction | |
324 var shouldRenderWaveAgain = wave.mouseUpStart ? !waveDissipated : !max
imumWave; | |
325 shouldRenderNextFrame = shouldRenderNextFrame || shouldRenderWaveAgain
; | |
326 if (!shouldKeepWave || this.cancelled) { | |
327 deleteTheseWaves.push(wave); | |
328 } | |
329 } | |
330 | |
331 if (shouldRenderNextFrame) { | |
332 requestAnimationFrame(this._loop); | |
333 } | |
334 | |
335 for (var i = 0; i < deleteTheseWaves.length; ++i) { | |
336 var wave = deleteTheseWaves[i]; | |
337 removeWaveFromScope(this, wave); | |
338 } | |
339 | |
340 if (!this.waves.length && this._loop) { | |
341 // clear the background color | |
342 this.$.bg.style.backgroundColor = null; | |
343 this._loop = null; | |
344 this.fire('core-transitionend'); | |
345 } | |
346 } | |
347 | |
348 }); | |
349 | |
350 })(); | |
351 | |
OLD | NEW |