5 var waveMaxRadius
= 150;
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
);
20 var size
= waveRadius
* (1 - Math
.pow(80, -tt
));
21 return Math
.abs(size
);
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
;
30 if (tu
<= 0) { // before touch up
31 return anim
.initialOpacity
;
33 return Math
.max(0, anim
.initialOpacity
- touchUp
* anim
.opacityDecayVelocity
);
36 function waveOuterOpacityFn(td
, tu
, anim
) {
37 // Convert from ms to s.
38 var touchDown
= td
/ 1000;
39 var touchUp
= tu
/ 1000;
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
));
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
)) {
59 function waveAtMaximum(wave
, radius
, anim
) {
60 var waveOpacity
= waveOpacityFn(wave
.tDown
, wave
.tUp
, anim
);
61 if (waveOpacity
>= anim
.initialOpacity
&& radius
>= Math
.min(wave
.maxRadius
, waveMaxRadius
)) {
70 function drawRipple(ctx
, x
, y
, radius
, innerColor
, outerColor
) {
72 ctx
.fillStyle
= outerColor
;
73 ctx
.fillRect(0,0,ctx
.canvas
.width
, ctx
.canvas
.height
);
76 ctx
.arc(x
, y
, radius
, 0, 2 * Math
.PI
, false);
77 ctx
.fillStyle
= innerColor
;
84 function createWave(elem
) {
85 var elementStyle
= window
.getComputedStyle(elem
);
86 var fgColor
= elementStyle
.color
;
100 function removeWaveFromScope(scope
, wave
) {
102 var pos
= scope
.waves
.indexOf(wave
);
103 scope
.waves
.splice(pos
, 1);
110 if (window
.performance
&& performance
.now
) {
111 now
= performance
.now
.bind(performance
);
114 function cssColorWithAlpha(cssColor
, alpha
) {
115 var parts
= cssColor
.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
116 if (typeof alpha
== 'undefined') {
120 return 'rgba(255, 255, 255, ' + alpha
+ ')';
122 return 'rgba(' + parts
[1] + ', ' + parts
[2] + ', ' + parts
[3] + ', ' + alpha
+ ')';
125 function dist(p1
, p2
) {
126 return Math
.sqrt(pow(p1
.x
- p2
.x
, 2) + pow(p1
.y
- p2
.y
, 2));
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
);
137 Polymer('paper-ripple', {
140 * The initial opacity set on the wave.
142 * @attribute initialOpacity
146 initialOpacity
: 0.25,
149 * How fast (opacity per second) the wave fades out.
151 * @attribute opacityDecayVelocity
155 opacityDecayVelocity
: 0.8,
157 backgroundFill
: true,
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
;
183 setupCanvas: function() {
184 this.$.canvas
.setAttribute('width', this.$.canvas
.clientWidth
* this.pixelDensity
+ "px");
185 this.$.canvas
.setAttribute('height', this.$.canvas
.clientHeight
* this.pixelDensity
+ "px");
186 var ctx
= this.$.canvas
.getContext('2d');
187 ctx
.scale(this.pixelDensity
, this.pixelDensity
);
189 this._loop
= this.animate
.bind(this, ctx
);
193 downAction: function(e
) {
195 var wave
= createWave(this.$.canvas
);
197 this.cancelled
= false;
198 wave
.isMouseDown
= true;
201 wave
.mouseUpStart
= 0.0;
202 wave
.mouseDownStart
= now();
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
;
210 wave
.startPosition
= {x
:touchX
, y
:touchY
};
212 if (this.classList
.contains("recenteringTouch")) {
213 wave
.endPosition
= {x
: width
/ 2, y
: height
/ 2};
214 wave
.slideDistance
= dist(wave
.startPosition
, wave
.endPosition
);
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
);
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;
234 this._loop
&& requestAnimationFrame(this._loop
);
238 this.cancelled
= true;
241 animate: function(ctx
) {
242 var shouldRenderNextFrame
= false;
245 ctx
.clearRect(0, 0, ctx
.canvas
.width
, ctx
.canvas
.height
);
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
255 initialOpacity
: this.initialOpacity
,
256 opacityDecayVelocity
: this.opacityDecayVelocity
,
257 height
: ctx
.canvas
.height
,
258 width
: ctx
.canvas
.width
261 for (var i
= 0; i
< this.waves
.length
; i
++) {
262 var wave
= this.waves
[i
];
264 if (wave
.mouseDownStart
> 0) {
265 wave
.tDown
= now() - wave
.mouseDownStart
;
267 if (wave
.mouseUpStart
> 0) {
268 wave
.tUp
= now() - wave
.mouseUpStart
;
271 // Determine how long the touch has been up or down.
273 var tDown
= wave
.tDown
;
274 longestTouchDownDuration
= Math
.max(longestTouchDownDuration
, tDown
);
275 longestTouchUpDuration
= Math
.max(longestTouchUpDuration
, tUp
);
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
;
283 // Position of the ripple.
284 var x
= wave
.startPosition
.x
;
285 var y
= wave
.startPosition
.y
;
287 // Ripple gravitational pull to the center of the canvas.
288 if (wave
.endPosition
) {
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) );
293 x
+= translateFraction
* (wave
.endPosition
.x
- wave
.startPosition
.x
);
294 y
+= translateFraction
* (wave
.endPosition
.y
- wave
.startPosition
.y
);
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
);
305 drawRipple(ctx
, x
, y
, radius
, waveColor
, bgFillColor
);
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
);
318 if (shouldRenderNextFrame
) {
319 requestAnimationFrame(this._loop
);
322 for (var i
= 0; i
< deleteTheseWaves
.length
; ++i
) {
323 var wave
= deleteTheseWaves
[i
];
324 removeWaveFromScope(this, wave
);
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
);