7 // Animation constants.
9 var waveOpacityDecayVelocity
= 0.8 / globalSpeed
; // opacity per second.
10 var waveInitialOpacity
= 0.25;
11 var waveLingerOnTouchUp
= 0.2;
12 var waveMaxRadius
= 150;
15 // - rather than max distance to corner, use hypotenuos(sp) (diag)
16 // - use quadratic for the fall off, move fast at the beginning,
17 // - on cancel, immediately fade out, reverse the direction
19 function waveRadiusFn(touchDownMs
, touchUpMs
, ww
, hh
) {
20 // Convert from ms to s.
21 var touchDown
= touchDownMs
/ 1000;
22 var touchUp
= touchUpMs
/ 1000;
23 var totalElapsed
= touchDown
+ touchUp
;
24 var waveRadius
= Math
.min(Math
.max(ww
, hh
), waveMaxRadius
) * 1.1 + 5;
25 var dduration
= 1.1 - .2 * (waveRadius
/ waveMaxRadius
);
26 var tt
= (totalElapsed
/ dduration
);
28 var ssize
= waveRadius
* (1 - Math
.pow(80, -tt
));
29 return Math
.abs(ssize
);
32 function waveOpacityFn(td
, tu
) {
33 // Convert from ms to s.
34 var touchDown
= td
/ 1000;
35 var touchUp
= tu
/ 1000;
36 var totalElapsed
= touchDown
+ touchUp
;
38 if (tu
<= 0) { // before touch up
39 return waveInitialOpacity
;
41 return Math
.max(0, waveInitialOpacity
- touchUp
* waveOpacityDecayVelocity
);
44 function waveOuterOpacityFn(td
, tu
) {
45 // Convert from ms to s.
46 var touchDown
= td
/ 1000;
47 var touchUp
= tu
/ 1000;
49 // Linear increase in background opacity, capped at the opacity
50 // of the wavefront (waveOpacity).
51 var outerOpacity
= touchDown
* 0.3;
52 var waveOpacity
= waveOpacityFn(td
, tu
);
53 return Math
.max(0, Math
.min(outerOpacity
, waveOpacity
));
57 function waveGravityToCenterPercentageFn(td
, tu
, r
) {
58 // Convert from ms to s.
59 var touchDown
= td
/ 1000;
60 var touchUp
= tu
/ 1000;
61 var totalElapsed
= touchDown
+ touchUp
;
63 return Math
.min(1.0, touchUp
* 6);
67 // Determines whether the wave should be completely removed.
68 function waveDidFinish(wave
, radius
) {
69 var waveOpacity
= waveOpacityFn(wave
.tDown
, wave
.tUp
);
70 // Does not linger any more.
71 // var lingerTimeMs = waveLingerOnTouchUp * 1000;
73 // If the wave opacity is 0 and the radius exceeds the bounds
74 // of the element, then this is finished.
75 if (waveOpacity
< 0.01 && radius
>= wave
.maxRadius
) {
85 function animateIcon() {
86 var el
= document
.getElementById('button_toolbar0');
87 el
.classList
.add('animate');
88 setTimeout(function(){
89 el
.classList
.remove('animate');
90 el
.classList
.toggle('selected');
95 function drawRipple(canvas
, x
, y
, radius
, innerColor
, outerColor
, innerColorAlpha
, outerColorAlpha
) {
96 var ctx
= canvas
.getContext('2d');
98 ctx
.fillStyle
= outerColor
;
99 ctx
.fillRect(0,0,canvas
.width
, canvas
.height
);
103 ctx
.arc(x
, y
, radius
, 0, 2 * Math
.PI
, false);
104 ctx
.fillStyle
= innerColor
;
108 function drawLabel(canvas
, label
, fontSize
, color
, alignment
) {
109 var ctx
= canvas
.getContext('2d');
110 ctx
.font
= fontSize
+ 'px Helvetica';
112 var metrics
= ctx
.measureText(label
);
113 var width
= metrics
.width
;
114 var height
= metrics
.height
;
115 ctx
.fillStyle
= color
;
117 var xPos
= (canvas
.width
/2 - width)/2;
119 if (alignment
=== 'left') { xPos
= 16; }
121 ctx
.fillText(label
, xPos
, canvas
.height
/2 - (canvas.height/2 - fontSize
+2) / 2);
128 function createWave(elem
) {
129 var elementStyle
= window
.getComputedStyle(elem
);
130 var fgColor
= elementStyle
.color
;
144 function removeWaveFromScope(scope
, wave
) {
146 var pos
= scope
.waves
.indexOf(wave
);
147 scope
.waves
.splice(pos
, 1);
152 function setUpPaperByClass( classname
) {
153 var elems
= document
.querySelectorAll( classname
);
154 [].forEach
.call( elems
, function( el
) {
159 function setUpPaper(elem
) {
160 var pixelDensity
= 2;
162 var elementStyle
= window
.getComputedStyle(elem
);
163 var fgColor
= elementStyle
.color
;
164 var bgColor
= elementStyle
.backgroundColor
;
165 elem
.width
= elem
.clientWidth
;
166 elem
.setAttribute('width', elem
.clientWidth
* pixelDensity
+ "px");
167 elem
.setAttribute('height', elem
.clientHeight
* pixelDensity
+ "px");
169 var isButton
= elem
.classList
.contains( 'button' ) || elem
.classList
.contains( 'button_floating' ) | elem
.classList
.contains( 'button_menu' );
170 var isToolbarButton
= elem
.classList
.contains( 'button_toolbar' );
172 elem
.getContext('2d').scale(pixelDensity
, pixelDensity
)
175 backgroundFill
: true,
182 scope
.label
= elem
.getAttribute('value') || elementStyle
.content
;
183 scope
.labelFontSize
= elementStyle
.fontSize
.split("px")[0];
185 drawLabel(elem
, scope
.label
, scope
.labelFontSize
, fgColor
, elem
.style
.textAlign
);
189 // RENDER FOR EACH FRAME
191 var onFrame = function() {
192 var shouldRenderNextFrame
= false;
195 var ctx
= elem
.getContext('2d');
196 ctx
.clearRect(0, 0, elem
.width
, elem
.height
);
198 var deleteTheseWaves
= [];
199 // The oldest wave's touch down duration
200 var longestTouchDownDuration
= 0;
201 var longestTouchUpDuration
= 0;
202 // Save the last known wave color
203 var lastWaveColor
= null;
205 for (var i
= 0; i
< scope
.waves
.length
; i
++) {
206 var wave
= scope
.waves
[i
];
208 if (wave
.mouseDownStart
> 0) {
209 wave
.tDown
= now() - wave
.mouseDownStart
;
211 if (wave
.mouseUpStart
> 0) {
212 wave
.tUp
= now() - wave
.mouseUpStart
;
215 // Determine how long the touch has been up or down.
217 var tDown
= wave
.tDown
;
218 longestTouchDownDuration
= Math
.max(longestTouchDownDuration
, tDown
);
219 longestTouchUpDuration
= Math
.max(longestTouchUpDuration
, tUp
);
221 // Obtain the instantenous size and alpha of the ripple.
222 var radius
= waveRadiusFn(tDown
, tUp
, elem
.width
, elem
.height
);
223 var waveAlpha
= waveOpacityFn(tDown
, tUp
);
224 var waveColor
= cssColorWithAlpha(wave
.waveColor
, waveAlpha
);
225 lastWaveColor
= wave
.waveColor
;
227 // Position of the ripple.
228 var x
= wave
.startPosition
.x
;
229 var y
= wave
.startPosition
.y
;
231 // Ripple gravitational pull to the center of the canvas.
232 if (wave
.endPosition
) {
234 var translateFraction
= waveGravityToCenterPercentageFn(tDown
, tUp
, wave
.maxRadius
);
236 // This translates from the origin to the center of the view based on the max dimension of
237 var translateFraction
= Math
.min(1, radius
/ wave
.containerSize
* 2 / Math
.sqrt(2) );
239 x
+= translateFraction
* (wave
.endPosition
.x
- wave
.startPosition
.x
);
240 y
+= translateFraction
* (wave
.endPosition
.y
- wave
.startPosition
.y
);
243 // If we do a background fill fade too, work out the correct color.
244 var bgFillColor
= null;
245 if (scope
.backgroundFill
) {
246 var bgFillAlpha
= waveOuterOpacityFn(tDown
, tUp
);
247 bgFillColor
= cssColorWithAlpha(wave
.waveColor
, bgFillAlpha
);
251 drawRipple(elem
, x
, y
, radius
, waveColor
, bgFillColor
);
253 // Determine whether there is any more rendering to be done.
254 var shouldRenderWaveAgain
= !waveDidFinish(wave
, radius
);
255 shouldRenderNextFrame
= shouldRenderNextFrame
|| shouldRenderWaveAgain
;
256 if (!shouldRenderWaveAgain
) {
257 deleteTheseWaves
.push(wave
);
261 if (shouldRenderNextFrame
) {
262 window
.requestAnimationFrame(onFrame
);
264 // If there is nothing to draw, clear any drawn waves now because
265 // we're not going to get another requestAnimationFrame any more.
266 var ctx
= elem
.getContext('2d');
267 ctx
.clearRect(0, 0, elem
.width
, elem
.height
);
270 // Draw the label at the very last point so it is on top of everything.
271 drawLabel(elem
, scope
.label
, scope
.labelFontSize
, fgColor
, elem
.style
.textAlign
);
273 for (var i
= 0; i
< deleteTheseWaves
.length
; ++i
) {
274 var wave
= deleteTheseWaves
[i
];
275 removeWaveFromScope(scope
, wave
);
280 // MOUSE DOWN HANDLER
283 elem
.addEventListener('mousedown', function(e
) {
284 var wave
= createWave(e
.target
);
285 var elem
= scope
.element
;
287 wave
.isMouseDown
= true;
290 wave
.mouseUpStart
= 0.0;
291 wave
.mouseDownStart
= now();
293 var width
= e
.target
.width
/ 2; // Retina canvas
294 var height
= e
.target
.height
/ 2;
295 var touchX
= e
.clientX
- e
.target
.offsetLeft
- e
.target
.offsetParent
.offsetLeft
;
296 var touchY
= e
.clientY
- e
.target
.offsetTop
- e
.target
.offsetParent
.offsetTop
;
297 wave
.startPosition
= {x
:touchX
, y
:touchY
};
299 if (elem
.classList
.contains("recenteringTouch")) {
300 wave
.endPosition
= {x
: width
/ 2, y
: height
/ 2};
301 wave
.slideDistance
= dist(wave
.startPosition
, wave
.endPosition
);
303 wave
.containerSize
= Math
.max(width
, height
);
304 wave
.maxRadius
= distanceFromPointToFurthestCorner(wave
.startPosition
, {w
: width
, h
: height
});
305 elem
.classList
.add("activated");
306 scope
.waves
.push(wave
);
307 window
.requestAnimationFrame(onFrame
);
315 elem
.addEventListener('mouseup', function(e
) {
316 elem
.classList
.remove("activated");
318 for (var i
= 0; i
< scope
.waves
.length
; i
++) {
319 // Declare the next wave that has mouse down to be mouse'ed up.
320 var wave
= scope
.waves
[i
];
321 if (wave
.isMouseDown
) {
322 wave
.isMouseDown
= false
323 wave
.mouseUpStart
= now();
324 wave
.mouseDownStart
= 0;
332 elem
.addEventListener('mouseout', function(e
) {
333 elem
.classList
.remove("activated");
335 for (var i
= 0; i
< scope
.waves
.length
; i
++) {
336 // Declare the next wave that has mouse down to be mouse'ed up.
337 var wave
= scope
.waves
[i
];
338 if (wave
.isMouseDown
) {
339 wave
.isMouseDown
= false
340 wave
.mouseUpStart
= now();
341 wave
.mouseDownStart
= 0;
343 wave
.cancelled
= true;
355 var now = function() { return new Date().getTime(); };
357 // Quad beizer where t is between 0 and 1.
358 function quadBezier(t
, p0
, p1
, p2
, p3
) {
359 return pow(1 - t
, 3) * p0
+
360 3 * pow(1 - t
, 2) * t
* p1
+
361 (1 - t
) * pow(t
, 2) * p2
+
366 return quadBezier(t
, 0.4, 0.0, 1, 1);
369 function cssColorWithAlpha(cssColor
, alpha
) {
370 var parts
= cssColor
.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
371 if (typeof alpha
== 'undefined') {
375 return 'rgba(255, 255, 255, ' + alpha
+ ')';
377 return 'rgba(' + parts
[1] + ', ' + parts
[2] + ', ' + parts
[3] + ', ' + alpha
+ ')';
380 function dist(p1
, p2
) {
381 return Math
.sqrt(Math
.pow(p1
.x
- p2
.x
, 2) + Math
.pow(p1
.y
- p2
.y
, 2));
384 function distanceFromPointToFurthestCorner(point
, size
) {
385 var tl_d
= dist(point
, {x
: 0, y
: 0});
386 var tr_d
= dist(point
, {x
: size
.w
, y
: 0});
387 var bl_d
= dist(point
, {x
: 0, y
: size
.h
});
388 var br_d
= dist(point
, {x
: size
.w
, y
: size
.h
});
389 return Math
.max(Math
.max(tl_d
, tr_d
), Math
.max(bl_d
, br_d
));
393 function toggleDialog() {
394 var el
= document
.getElementById('dialog');
395 el
.classList
.toggle("visible");
398 function toggleMenu() {
399 var el
= document
.getElementById('menu');
400 el
.classList
.toggle("visible");
407 setUpPaperByClass( '.paper' );
410 window
.addEventListener('DOMContentLoaded', init
, false);