1 <html>
2 <head>
3 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
4 <script>
5
6 //
7 // INK EQUATIONS
8 //
9
10 // Animation constants.
11 var globalSpeed = 1;
12 var waveOpacityDecayVelocity = 0.8 / globalSpeed; // opacity per second.
13 var waveInitialOpacity = 0.25;
14 var waveLingerOnTouchUp = 0.2;
15 var waveMaxRadius = 150;
16
17 // TODOs:
18 // - rather than max distance to corner, use hypotenuos(sp) (diag)
19 // - use quadratic for the fall off, move fast at the beginning,
20 // - on cancel, immediately fade out, reverse the direction
21
22 function waveRadiusFn(touchDownMs, touchUpMs, ww, hh) {
23 // Convert from ms to s.
24 var touchDown = touchDownMs / 1000;
25 var touchUp = touchUpMs / 1000;
26 var totalElapsed = touchDown + touchUp;
27 var waveRadius = Math.min(Math.max(ww, hh), waveMaxRadius) * 1.1 + 5;
28 var dduration = 1.1 - .2 * (waveRadius / waveMaxRadius);
29 var tt = (totalElapsed / dduration);
30
31 var ssize = waveRadius * (1 - Math.pow(80, -tt));
32 return Math.abs(ssize);
33 }
34
35 function waveOpacityFn(td, tu) {
36 // Convert from ms to s.
37 var touchDown = td / 1000;
38 var touchUp = tu / 1000;
39 var totalElapsed = touchDown + touchUp;
40
41 if (tu <= 0) { // before touch up
42 return waveInitialOpacity;
43 }
44 return Math.max(0, waveInitialOpacity - touchUp * waveOpacityDecayVelocity);
45 }
46
47 function waveOuterOpacityFn(td, tu) {
48 // Convert from ms to s.
49 var touchDown = td / 1000;
50 var touchUp = tu / 1000;
51
52 // Linear increase in background opacity, capped at the opacity
53 // of the wavefront (waveOpacity).
54 var outerOpacity = touchDown * 0.3;
55 var waveOpacity = waveOpacityFn(td, tu);
56 return Math.max(0, Math.min(outerOpacity, waveOpacity));
57
58 }
59
60 function waveGravityToCenterPercentageFn(td, tu, r) {
61 // Convert from ms to s.
62 var touchDown = td / 1000;
63 var touchUp = tu / 1000;
64 var totalElapsed = touchDown + touchUp;
65
66 return Math.min(1.0, touchUp * 6);
67 }
68
69
70 // Determines whether the wave should be completely removed.
71 function waveDidFinish(wave, radius) {
72 var waveOpacity = waveOpacityFn(wave.tDown, wave.tUp);
73 // Does not linger any more.
74 // var lingerTimeMs = waveLingerOnTouchUp * 1000;
75
76 // If the wave opacity is 0 and the radius exceeds the bounds
77 // of the element, then this is finished.
78 if (waveOpacity < 0.01 && radius >= wave.maxRadius) {
79 return true;
80 }
81 return false;
82 };
83
84 //
85 // DRAWING
86 //
87
88 function animateIcon() {
89 var el = document.getElementById('button_toolbar0');
90 el.classList.add('animate');
91 setTimeout(function(){
92 el.classList.remove('animate');
93 el.classList.toggle('selected');
94 }, 500);
95 }
96
97
98 function drawRipple(canvas, x, y, radius, innerColor, outerColor, innerColorAlpha, outerColorAlpha) {
99 var ctx = canvas.getContext('2d');
100 if (outerColor) {
101 ctx.fillStyle = outerColor;
102 ctx.fillRect(0,0,canvas.width, canvas.height);
103 }
104
105 ctx.beginPath();
106 ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
107 ctx.fillStyle = innerColor;
108 ctx.fill();
109 }
110
111 function drawLabel(canvas, label, fontSize, color, alignment) {
112 var ctx = canvas.getContext('2d');
113 ctx.font= fontSize + 'px Helvetica';
114
115 var metrics = ctx.measureText(label);
116 var width = metrics.width;
117 var height = metrics.height;
118 ctx.fillStyle = color;
119
120 var xPos = (canvas.width/2 - width)/2;
121
122 if (alignment === 'left') { xPos = 16; }
123
124 ctx.fillText(label, xPos, canvas.height/2 - (canvas.height/2 - fontSize +2) / 2);
125 }
126
127 //
128 // BUTTON SETUP
129 //
130
131 function createWave(elem) {
132 var elementStyle = window.getComputedStyle(elem);
133 var fgColor = elementStyle.color;
134
135 var wave = {
136 waveColor: fgColor,
137 maxRadius: 0,
138 isMouseDown: false,
139 mouseDownStart: 0.0,
140 mouseUpStart: 0.0,
141 tDown: 0,
142 tUp: 0
143 };
144 return wave;
145 }
146
147 function removeWaveFromScope(scope, wave) {
148 if (scope.waves) {
149 var pos = scope.waves.indexOf(wave);
150 scope.waves.splice(pos, 1);
151 }
152 };
153
154
155 function setUpPaperByClass( classname ) {
156 var elems = document.querySelectorAll( classname );
157 [].forEach.call( elems, function( el ) {
158 setUpPaper(el);
159 });
160 }
161
162 function setUpPaper(elem) {
163 var pixelDensity = 2;
164
165 var elementStyle = window.getComputedStyle(elem);
166 var fgColor = elementStyle.color;
167 var bgColor = elementStyle.backgroundColor;
168 elem.width = elem.clientWidth;
169 elem.setAttribute('width', elem.clientWidth * pixelDensity + "px");
170 elem.setAttribute('height', elem.clientHeight * pixelDensity + "px");
171
172 var isButton = elem.classList.contains( 'button' ) || elem.classList.contains( 'button_floating' ) | elem.classList.contains( 'button_menu' );
173 var isToolbarButton = elem.classList.contains( 'button_toolbar' );
174
175 elem.getContext('2d').scale(pixelDensity, pixelDensity)
176
177 var scope = {
178 backgroundFill: true,
179 element: elem,
180 label: 'Button',
181 waves: [],
182 };
183
184
185 scope.label = elem.getAttribute('value') || elementStyle.content;
186 scope.labelFontSize = elementStyle.fontSize.split("px")[0];
187
188 drawLabel(elem, scope.label, scope.labelFontSize, fgColor, elem.style.textAlign);
189
190
191 //
192 // RENDER FOR EACH FRAME
193 //
194 var onFrame = function() {
195 var shouldRenderNextFrame = false;
196
197 // Clear the canvas
198 var ctx = elem.getContext('2d');
199 ctx.clearRect(0, 0, elem.width, elem.height);
200
201 var deleteTheseWaves = [];
202 // The oldest wave's touch down duration
203 var longestTouchDownDuration = 0;
204 var longestTouchUpDuration = 0;
205 // Save the last known wave color
206 var lastWaveColor = null;
207
208 for (var i = 0; i < scope.waves.length; i++) {
209 var wave = scope.waves[i];
210
211 if (wave.mouseDownStart > 0) {
212 wave.tDown = now() - wave.mouseDownStart;
213 }
214 if (wave.mouseUpStart > 0) {
215 wave.tUp = now() - wave.mouseUpStart;
216 }
217
218 // Determine how long the touch has been up or down.
219 var tUp = wave.tUp;
220 var tDown = wave.tDown;
221 longestTouchDownDuration = Math.max(longestTouchDownDuration, tDown);
222 longestTouchUpDuration = Math.max(longestTouchUpDuration, tUp);
223
224 // Obtain the instantenous size and alpha of the ripple.
225 var radius = waveRadiusFn(tDown, tUp, elem.width, elem.height);
226 var waveAlpha = waveOpacityFn(tDown, tUp);
227 var waveColor = cssColorWithAlpha(wave.waveColor, waveAlpha);
228 lastWaveColor = wave.waveColor;
229
230 // Position of the ripple.
231 var x = wave.startPosition.x;
232 var y = wave.startPosition.y;
233
234 // Ripple gravitational pull to the center of the canvas.
235 if (wave.endPosition) {
236
237 var translateFraction = waveGravityToCenterPercentageFn(tDown, tUp, wave.maxRadius);
238
239 // This translates from the origin to the center of the view based on the max dimension of
240 var translateFraction = Math.min(1, radius / wave.containerSize * 2 / Math.sqrt(2) );
241
242 x += translateFraction * (wave.endPosition.x - wave.startPosition.x);
243 y += translateFraction * (wave.endPosition.y - wave.startPosition.y);
244 }
245
246 // If we do a background fill fade too, work out the correct color.
247 var bgFillColor = null;
248 if (scope.backgroundFill) {
249 var bgFillAlpha = waveOuterOpacityFn(tDown, tUp);
250 bgFillColor = cssColorWithAlpha(wave.waveColor, bgFillAlpha);
251 }
252
253 // Draw the ripple.
254 drawRipple(elem, x, y, radius, waveColor, bgFillColor);
255
256 // Determine whether there is any more rendering to be done.
257 var shouldRenderWaveAgain = !waveDidFinish(wave, radius);
258 shouldRenderNextFrame = shouldRenderNextFrame || shouldRenderWaveAgain;
259 if (!shouldRenderWaveAgain) {
260 deleteTheseWaves.push(wave);
261 }
262 }
263
264 if (shouldRenderNextFrame) {
265 window.requestAnimationFrame(onFrame);
266 } else {
267 // If there is nothing to draw, clear any drawn waves now because
268 // we're not going to get another requestAnimationFrame any more.
269 var ctx = elem.getContext('2d');
270 ctx.clearRect(0, 0, elem.width, elem.height);
271 }
272
273 // Draw the label at the very last point so it is on top of everything.
274 drawLabel(elem, scope.label, scope.labelFontSize, fgColor, elem.style.textAlign);
275
276 for (var i = 0; i < deleteTheseWaves.length; ++i) {
277 var wave = deleteTheseWaves[i];
278 removeWaveFromScope(scope, wave);
279 }
280 };
281
282 //
283 // MOUSE DOWN HANDLER
284 //
285
286 elem.addEventListener('mousedown', function(e) {
287 var wave = createWave(e.target);
288 var elem = scope.element;
289
290 wave.isMouseDown = true;
291 wave.tDown = 0.0;
292 wave.tUp = 0.0;
293 wave.mouseUpStart = 0.0;
294 wave.mouseDownStart = now();
295
296 var width = e.target.width / 2; // Retina canvas
297 var height = e.target.height / 2;
298 var touchX = e.clientX - e.target.offsetLeft - e.target.offsetParent.offsetLeft;
299 var touchY = e.clientY - e.target.offsetTop - e.target.offsetParent.offsetTop;
300 wave.startPosition = {x:touchX, y:touchY};
301
302 if (elem.classList.contains("recenteringTouch")) {
303 wave.endPosition = {x: width / 2, y: height / 2};
304 wave.slideDistance = dist(wave.startPosition, wave.endPosition);
305 }
306 wave.containerSize = Math.max(width, height);
307 wave.maxRadius = distanceFromPointToFurthestCorner(wave.startPosition, {w: width, h: height});
308 elem.classList.add("activated");
309 scope.waves.push(wave);
310 window.requestAnimationFrame(onFrame);
311 return false;
312 });
313
314 //
315 // MOUSE UP HANDLER
316 //
317
318 elem.addEventListener('mouseup', function(e) {
319 elem.classList.remove("activated");
320
321 for (var i = 0; i < scope.waves.length; i++) {
322 // Declare the next wave that has mouse down to be mouse'ed up.
323 var wave = scope.waves[i];
324 if (wave.isMouseDown) {
325 wave.isMouseDown = false
326 wave.mouseUpStart = now();
327 wave.mouseDownStart = 0;
328 wave.tUp = 0.0;
329 break;
330 }
331 }
332 return false;
333 });
334
335 elem.addEventListener('mouseout', function(e) {
336 elem.classList.remove("activated");
337
338 for (var i = 0; i < scope.waves.length; i++) {
339 // Declare the next wave that has mouse down to be mouse'ed up.
340 var wave = scope.waves[i];
341 if (wave.isMouseDown) {
342 wave.isMouseDown = false
343 wave.mouseUpStart = now();
344 wave.mouseDownStart = 0;
345 wave.tUp = 0.0;
346 wave.cancelled = true;
347 break;
348 }
349 }
350 return false;
351 });
352
353 return scope;
354 };
355
356 // Shortcuts.
357 var pow = Math.pow;
358 var now = function() { return new Date().getTime(); };
359
360 // Quad beizer where t is between 0 and 1.
361 function quadBezier(t, p0, p1, p2, p3) {
362 return pow(1 - t, 3) * p0 +
363 3 * pow(1 - t, 2) * t * p1 +
364 (1 - t) * pow(t, 2) * p2 +
365 pow(t, 3) * p3;
366 }
367
368 function easeIn(t) {
369 return quadBezier(t, 0.4, 0.0, 1, 1);
370 }
371
372 function cssColorWithAlpha(cssColor, alpha) {
373 var parts = cssColor.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
374 if (typeof alpha == 'undefined') {
375 alpha = 1;
376 }
377 if (!parts) {
378 return 'rgba(255, 255, 255, ' + alpha + ')';
379 }
380 return 'rgba(' + parts[1] + ', ' + parts[2] + ', ' + parts[3] + ', ' + alpha + ')';
381 }
382
383 function dist(p1, p2) {
384 return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
385 }
386
387 function distanceFromPointToFurthestCorner(point, size) {
388 var tl_d = dist(point, {x: 0, y: 0});
389 var tr_d = dist(point, {x: size.w, y: 0});
390 var bl_d = dist(point, {x: 0, y: size.h});
391 var br_d = dist(point, {x: size.w, y: size.h});
392 return Math.max(Math.max(tl_d, tr_d), Math.max(bl_d, br_d));
393 }
394
395
396 function toggleDialog() {
397 var el = document.getElementById('dialog');
398 el.classList.toggle("visible");
399 }
400
401 function toggleMenu() {
402 var el = document.getElementById('menu');
403 el.classList.toggle("visible");
404 }
405
406
407 // Initialize
408
409 function init() {
410 setUpPaperByClass( '.paper' );
411 }
412
413 window.addEventListener('DOMContentLoaded', init, false);
414
415
416
749
750
751
752
761
762
763 Press mah buttons!
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.
764
765
766
767
768
775
776
777 Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam. sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
778
779
780
781
782
783
784
785
786
787
788
789
790