Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / polymer / components / paper-ripple / paper-ripple.html
1 <!--
2 Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
3 This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
4 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
5 The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
6 Code distributed by Google as part of the polymer project is also
7 subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
8 -->
9
10 <!--
11 `paper-ripple` provides a visual effect that other paper elements can
12 use to simulate a rippling effect emanating from the point of contact.  The
13 effect can be visualized as a concentric circle with motion.
14
15 Example:
16
17     <paper-ripple></paper-ripple>
18
19 `paper-ripple` listens to "down" and "up" events so it would display ripple
20 effect when touches on it.  You can also defeat the default behavior and 
21 manually route the down and up actions to the ripple element.  Note that it is
22 important if you call downAction() you will have to make sure to call upAction()
23 so that `paper-ripple` would end the animation loop.
24
25 Example:
26
27     <paper-ripple id="ripple" style="pointer-events: none;"></paper-ripple>
28     ...
29     downAction: function(e) {
30       this.$.ripple.downAction({x: e.x, y: e.y});
31     },
32     upAction: function(e) {
33       this.$.ripple.upAction();
34     }
35
36 Styling ripple effect:
37
38   Use CSS color property to style the ripple:
39
40     paper-ripple {
41       color: #4285f4;
42     }
43
44   Note that CSS color property is inherited so it is not required to set it on
45   the `paper-ripple` element directly.
46
47 Apply `recenteringTouch` class to make the recentering rippling effect.
48
49     <paper-ripple class="recenteringTouch"></paper-ripple>
50
51 Apply `circle` class to make the rippling effect within a circle.
52
53     <paper-ripple class="circle"></paper-ripple>
54
55 @group Paper Elements
56 @element paper-ripple
57 @homepage github.io
58 -->
59
60 <link rel="import" href="../polymer/polymer.html" >
61
62 <polymer-element name="paper-ripple" attributes="initialOpacity opacityDecayVelocity">
63 <template>
64
65   <style>
66
67     :host {
68       display: block;
69       position: relative;
70     }
71
72     #canvas {
73       pointer-events: none;
74       position: absolute;
75       top: 0;
76       left: 0;
77       width: 100%;
78       height: 100%;
79     }
80
81     :host(.circle) #canvas {
82       border-radius: 50%;
83     }
84
85   </style>
86
87 </template>
88 <script>
89
90   (function() {
91
92     var waveMaxRadius = 150;
93     //
94     // INK EQUATIONS
95     //
96     function waveRadiusFn(touchDownMs, touchUpMs, anim) {
97       // Convert from ms to s.
98       var touchDown = touchDownMs / 1000;
99       var touchUp = touchUpMs / 1000;
100       var totalElapsed = touchDown + touchUp;
101       var ww = anim.width, hh = anim.height;
102       // use diagonal size of container to avoid floating point math sadness
103       var waveRadius = Math.min(Math.sqrt(ww * ww + hh * hh), waveMaxRadius) * 1.1 + 5;
104       var duration = 1.1 - .2 * (waveRadius / waveMaxRadius);
105       var tt = (totalElapsed / duration);
106
107       var size = waveRadius * (1 - Math.pow(80, -tt));
108       return Math.abs(size);
109     }
110
111     function waveOpacityFn(td, tu, anim) {
112       // Convert from ms to s.
113       var touchDown = td / 1000;
114       var touchUp = tu / 1000;
115       var totalElapsed = touchDown + touchUp;
116
117       if (tu <= 0) {  // before touch up
118         return anim.initialOpacity;
119       }
120       return Math.max(0, anim.initialOpacity - touchUp * anim.opacityDecayVelocity);
121     }
122
123     function waveOuterOpacityFn(td, tu, anim) {
124       // Convert from ms to s.
125       var touchDown = td / 1000;
126       var touchUp = tu / 1000;
127
128       // Linear increase in background opacity, capped at the opacity
129       // of the wavefront (waveOpacity).
130       var outerOpacity = touchDown * 0.3;
131       var waveOpacity = waveOpacityFn(td, tu, anim);
132       return Math.max(0, Math.min(outerOpacity, waveOpacity));
133     }
134
135     // Determines whether the wave should be completely removed.
136     function waveDidFinish(wave, radius, anim) {
137       var waveOpacity = waveOpacityFn(wave.tDown, wave.tUp, anim);
138       // If the wave opacity is 0 and the radius exceeds the bounds
139       // of the element, then this is finished.
140       if (waveOpacity < 0.01 && radius >= Math.min(wave.maxRadius, waveMaxRadius)) {
141         return true;
142       }
143       return false;
144     };
145
146     function waveAtMaximum(wave, radius, anim) {
147       var waveOpacity = waveOpacityFn(wave.tDown, wave.tUp, anim);
148       if (waveOpacity >= anim.initialOpacity && radius >= Math.min(wave.maxRadius, waveMaxRadius)) {
149         return true;
150       }
151       return false;
152     }
153
154     //
155     // DRAWING
156     //
157     function drawRipple(ctx, x, y, radius, innerColor, outerColor) {
158       if (outerColor) {
159         ctx.fillStyle = outerColor;
160         ctx.fillRect(0,0,ctx.canvas.width, ctx.canvas.height);
161       }
162       ctx.beginPath();
163       ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
164       ctx.fillStyle = innerColor;
165       ctx.fill();
166     }
167
168     //
169     // SETUP
170     //
171     function createWave(elem) {
172       var elementStyle = window.getComputedStyle(elem);
173       var fgColor = elementStyle.color;
174
175       var wave = {
176         waveColor: fgColor,
177         maxRadius: 0,
178         isMouseDown: false,
179         mouseDownStart: 0.0,
180         mouseUpStart: 0.0,
181         tDown: 0,
182         tUp: 0
183       };
184       return wave;
185     }
186
187     function removeWaveFromScope(scope, wave) {
188       if (scope.waves) {
189         var pos = scope.waves.indexOf(wave);
190         scope.waves.splice(pos, 1);
191       }
192     };
193
194     // Shortcuts.
195     var pow = Math.pow;
196     var now = Date.now;
197     if (window.performance && performance.now) {
198       now = performance.now.bind(performance);
199     }
200
201     function cssColorWithAlpha(cssColor, alpha) {
202         var parts = cssColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
203         if (typeof alpha == 'undefined') {
204             alpha = 1;
205         }
206         if (!parts) {
207           return 'rgba(255, 255, 255, ' + alpha + ')';
208         }
209         return 'rgba(' + parts[1] + ', ' + parts[2] + ', ' + parts[3] + ', ' + alpha + ')';
210     }
211
212     function dist(p1, p2) {
213       return Math.sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2));
214     }
215
216     function distanceFromPointToFurthestCorner(point, size) {
217       var tl_d = dist(point, {x: 0, y: 0});
218       var tr_d = dist(point, {x: size.w, y: 0});
219       var bl_d = dist(point, {x: 0, y: size.h});
220       var br_d = dist(point, {x: size.w, y: size.h});
221       return Math.max(tl_d, tr_d, bl_d, br_d);
222     }
223
224     Polymer('paper-ripple', {
225
226       /**
227        * The initial opacity set on the wave.
228        *
229        * @attribute initialOpacity
230        * @type number
231        * @default 0.25
232        */
233       initialOpacity: 0.25,
234
235       /**
236        * How fast (opacity per second) the wave fades out.
237        *
238        * @attribute opacityDecayVelocity
239        * @type number
240        * @default 0.8
241        */
242       opacityDecayVelocity: 0.8,
243
244       backgroundFill: true,
245       pixelDensity: 2,
246
247       eventDelegates: {
248         down: 'downAction',
249         up: 'upAction'
250       },
251
252       attached: function() {
253         // create the canvas element manually becase ios
254         // does not render the canvas element if it is not created in the
255         // main document (component templates are created in a
256         // different document). See:
257         // https://bugs.webkit.org/show_bug.cgi?id=109073.
258         if (!this.$.canvas) {
259           var canvas = document.createElement('canvas');
260           canvas.id = 'canvas';
261           this.shadowRoot.appendChild(canvas);
262           this.$.canvas = canvas;
263         }
264       },
265
266       ready: function() {
267         this.waves = [];
268       },
269
270       setupCanvas: function() {
271         this.$.canvas.setAttribute('width', this.$.canvas.clientWidth * this.pixelDensity + "px");
272         this.$.canvas.setAttribute('height', this.$.canvas.clientHeight * this.pixelDensity + "px");
273         var ctx = this.$.canvas.getContext('2d');
274         ctx.scale(this.pixelDensity, this.pixelDensity);
275         if (!this._loop) {
276           this._loop = this.animate.bind(this, ctx);
277         }
278       },
279
280       downAction: function(e) {
281         this.setupCanvas();
282         var wave = createWave(this.$.canvas);
283
284         this.cancelled = false;
285         wave.isMouseDown = true;
286         wave.tDown = 0.0;
287         wave.tUp = 0.0;
288         wave.mouseUpStart = 0.0;
289         wave.mouseDownStart = now();
290
291         var width = this.$.canvas.width / 2; // Retina canvas
292         var height = this.$.canvas.height / 2;
293         var rect = this.getBoundingClientRect();
294         var touchX = e.x - rect.left;
295         var touchY = e.y - rect.top;
296
297         wave.startPosition = {x:touchX, y:touchY};
298
299         if (this.classList.contains("recenteringTouch")) {
300           wave.endPosition = {x: width / 2,  y: height / 2};
301           wave.slideDistance = dist(wave.startPosition, wave.endPosition);
302         }
303         wave.containerSize = Math.max(width, height);
304         wave.maxRadius = distanceFromPointToFurthestCorner(wave.startPosition, {w: width, h: height});
305         this.waves.push(wave);
306         requestAnimationFrame(this._loop);
307       },
308
309       upAction: function() {
310         for (var i = 0; i < this.waves.length; i++) {
311           // Declare the next wave that has mouse down to be mouse'ed up.
312           var wave = this.waves[i];
313           if (wave.isMouseDown) {
314             wave.isMouseDown = false
315             wave.mouseUpStart = now();
316             wave.mouseDownStart = 0;
317             wave.tUp = 0.0;
318             break;
319           }
320         }
321         this._loop && requestAnimationFrame(this._loop);
322       },
323
324       cancel: function() {
325         this.cancelled = true;
326       },
327
328       animate: function(ctx) {
329         var shouldRenderNextFrame = false;
330
331         // Clear the canvas
332         ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
333
334         var deleteTheseWaves = [];
335         // The oldest wave's touch down duration
336         var longestTouchDownDuration = 0;
337         var longestTouchUpDuration = 0;
338         // Save the last known wave color
339         var lastWaveColor = null;
340         // wave animation values
341         var anim = {
342           initialOpacity: this.initialOpacity,
343           opacityDecayVelocity: this.opacityDecayVelocity,
344           height: ctx.canvas.height,
345           width: ctx.canvas.width
346         }
347
348         for (var i = 0; i < this.waves.length; i++) {
349           var wave = this.waves[i];
350
351           if (wave.mouseDownStart > 0) {
352             wave.tDown = now() - wave.mouseDownStart;
353           }
354           if (wave.mouseUpStart > 0) {
355             wave.tUp = now() - wave.mouseUpStart;
356           }
357
358           // Determine how long the touch has been up or down.
359           var tUp = wave.tUp;
360           var tDown = wave.tDown;
361           longestTouchDownDuration = Math.max(longestTouchDownDuration, tDown);
362           longestTouchUpDuration = Math.max(longestTouchUpDuration, tUp);
363
364           // Obtain the instantenous size and alpha of the ripple.
365           var radius = waveRadiusFn(tDown, tUp, anim);
366           var waveAlpha =  waveOpacityFn(tDown, tUp, anim);
367           var waveColor = cssColorWithAlpha(wave.waveColor, waveAlpha);
368           lastWaveColor = wave.waveColor;
369
370           // Position of the ripple.
371           var x = wave.startPosition.x;
372           var y = wave.startPosition.y;
373
374           // Ripple gravitational pull to the center of the canvas.
375           if (wave.endPosition) {
376
377             // This translates from the origin to the center of the view  based on the max dimension of  
378             var translateFraction = Math.min(1, radius / wave.containerSize * 2 / Math.sqrt(2) );
379
380             x += translateFraction * (wave.endPosition.x - wave.startPosition.x);
381             y += translateFraction * (wave.endPosition.y - wave.startPosition.y);
382           }
383
384           // If we do a background fill fade too, work out the correct color.
385           var bgFillColor = null;
386           if (this.backgroundFill) {
387             var bgFillAlpha = waveOuterOpacityFn(tDown, tUp, anim);
388             bgFillColor = cssColorWithAlpha(wave.waveColor, bgFillAlpha);
389           }
390
391           // Draw the ripple.
392           drawRipple(ctx, x, y, radius, waveColor, bgFillColor);
393
394           // Determine whether there is any more rendering to be done.
395           var maximumWave = waveAtMaximum(wave, radius, anim);
396           var waveDissipated = waveDidFinish(wave, radius, anim);
397           var shouldKeepWave = !waveDissipated || maximumWave;
398           var shouldRenderWaveAgain = !waveDissipated && !maximumWave;
399           shouldRenderNextFrame = shouldRenderNextFrame || shouldRenderWaveAgain;
400           if (!shouldKeepWave || this.cancelled) {
401             deleteTheseWaves.push(wave);
402           }
403        }
404
405         if (shouldRenderNextFrame) {
406           requestAnimationFrame(this._loop);
407         }
408
409         for (var i = 0; i < deleteTheseWaves.length; ++i) {
410           var wave = deleteTheseWaves[i];
411           removeWaveFromScope(this, wave);
412         }
413
414         if (!this.waves.length) {
415           // If there is nothing to draw, clear any drawn waves now because
416           // we're not going to get another requestAnimationFrame any more.
417           ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
418           this._loop = null;
419         }
420       }
421
422     });
423
424   })();
425
426 </script>
427 </polymer-element>