Home | History | Annotate | Download | only in paper-ripple
      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 791