Inline scripts were extracted from Polymer elements.
[chromium-blink-merge.git] / third_party / polymer / components-chromium / paper-ripple / paper-ripple-extracted.js
blobf3458e4a9b99080626e6fd3dc3fdae3ab25c4e83
3   (function() {
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);
20       var size = waveRadius * (1 - Math.pow(80, -tt));
21       return Math.abs(size);
22     }
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;
32       }
33       return Math.max(0, anim.initialOpacity - touchUp * anim.opacityDecayVelocity);
34     }
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));
46     }
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     };
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)) {
62         return true;
63       }
64       return false;
65     }
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     }
81     //
82     // SETUP
83     //
84     function createWave(elem) {
85       var elementStyle = window.getComputedStyle(elem);
86       var fgColor = elementStyle.color;
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     }
100     function removeWaveFromScope(scope, wave) {
101       if (scope.waves) {
102         var pos = scope.waves.indexOf(wave);
103         scope.waves.splice(pos, 1);
104       }
105     };
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     }
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] + ', ' + alpha + ')';
123     }
125     function dist(p1, p2) {
126       return Math.sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
127     }
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     }
137     Polymer('paper-ripple', {
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,
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,
157       backgroundFill: true,
158       pixelDensity: 2,
160       eventDelegates: {
161         down: 'downAction',
162         up: 'upAction'
163       },
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       },
179       ready: function() {
180         this.waves = [];
181       },
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);
188         if (!this._loop) {
189           this._loop = this.animate.bind(this, ctx);
190         }
191       },
193       downAction: function(e) {
194         this.setupCanvas();
195         var wave = createWave(this.$.canvas);
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();
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);
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       },
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       },
237       cancel: function() {
238         this.cancelled = true;
239       },
241       animate: function(ctx) {
242         var shouldRenderNextFrame = false;
244         // Clear the canvas
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
254         var anim = {
255           initialOpacity: this.initialOpacity,
256           opacityDecayVelocity: this.opacityDecayVelocity,
257           height: ctx.canvas.height,
258           width: ctx.canvas.width
259         }
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;
266           }
267           if (wave.mouseUpStart > 0) {
268             wave.tUp = now() - wave.mouseUpStart;
269           }
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);
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);
295           }
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           }
304           // Draw the ripple.
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);
315           }
316        }
318         if (shouldRenderNextFrame) {
319           requestAnimationFrame(this._loop);
320         }
322         for (var i = 0; i < deleteTheseWaves.length; ++i) {
323           var wave = deleteTheseWaves[i];
324           removeWaveFromScope(this, wave);
325         }
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       }
335     });
337   })();