block by mbostock c66ab1426f4b8945a7ef

Wave Motion

Full Screen

A recreation of a lovely GIF by Dave Whyte. As the Exploratorium explains, “Wave motion at the surface of water is made up of small circular motions of parcels of water.”

Updated Example →

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>

canvas {
  position: absolute;
}

</style>
<canvas id="canvas-back" width="960" height="500"></canvas>
<canvas id="canvas-front" width="960" height="500"></canvas>
<script>

/* https://github.com/d3/d3-timer Copyright 2015 Mike Bostock */
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define("d3-timer",["exports"],e):e(t.d3_timer={})}(this,function(t){"use strict";function e(t,e,n){this.id=++c,this.restart(t,e,n)}function n(t,n,i){return new e(t,n,i)}function i(t){t=null==t?Date.now():+t,++l;try{for(var e,n=a;n;)t>=n.time&&(e=n.callback)(t-n.time,t),n=n.next}finally{--l}}function o(){l=f=0;try{i()}finally{for(var t,e=a,n=1/0;e;)e.callback?(n>e.time&&(n=e.time),e=(t=e).next):e=t?t.next=e.next:a=e.next;u=t,r(n)}}function r(t){if(!l){f&&(f=clearTimeout(f));var e=t-Date.now();e>24?1/0>t&&(f=setTimeout(o,e)):(l=1,s(o))}}var a,u,l=0,f=0,c=0,m={},s="undefined"!=typeof window&&(window.requestAnimationFrame||window.msRequestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.oRequestAnimationFrame)||function(t){return setTimeout(t,17)};e.prototype=n.prototype={restart:function(t,e,n){if("function"!=typeof t)throw new TypeError("callback is not a function");n=(null==n?Date.now():+n)+(null==e?0:+e);var i=this.id,o=m[i];o?(o.callback=t,o.time=n):(o={next:null,callback:t,time:n},u?u.next=o:a=o,m[i]=u=o),r()},stop:function(){var t=this.id,e=m[t];e&&(e.callback=null,e.time=1/0,delete m[t],r())}};var d="0.0.6";t.version=d,t.timer=n,t.timerFlush=i});

var canvasBack = document.getElementById("canvas-back"),
    canvasFront = document.getElementById("canvas-front");

var width = canvasBack.width,
    height = canvasBack.height,
    ringRadius = 24,
    ringSeparation = 1.1 * ringRadius,
    dotRadius = 3.5,
    n = (width + ringRadius) / ringSeparation,
    m = (height + ringRadius) / ringSeparation;

var contextBack,
    contextFront,
    scale = window.devicePixelRatio;

if (scale > 1) {
  canvasFront.style.width = canvasBack.style.width = width + "px";
  canvasFront.style.height = canvasBack.style.height = height + "px";
  canvasFront.width = canvasBack.width = width * scale;
  canvasFront.height = canvasBack.height = height * scale;
  contextBack = canvasBack.getContext("2d");
  contextFront = canvasFront.getContext("2d");
  contextBack.scale(scale, scale);
  contextFront.scale(scale, scale);
} else {
  contextBack = canvasBack.getContext("2d");
  contextFront = canvasFront.getContext("2d");
}

contextBack.strokeStyle = "#999";
for (var i = -1; i < n; ++i) {
  for (var j = -1; j < m; ++j) {
    contextBack.beginPath();
    contextBack.arc(i * ringSeparation, j * ringSeparation, ringRadius, 0, 2 * Math.PI);
    contextBack.stroke();
  }
}

d3_timer.timer(function(elapsed) {
  contextFront.clearRect(0, 0, width, height);
  for (var i = -1; i < n; ++i) {
    for (var j = -1; j < m; ++j) {
      contextFront.save();
      contextFront.beginPath();
      contextFront.translate(i * ringSeparation, j * ringSeparation);
      contextFront.rotate((i + j) / 6 + elapsed / 200);
      contextFront.arc(ringRadius, 0, dotRadius, 0, 2 * Math.PI);
      contextFront.fill();
      contextFront.restore();
    }
  }
});

</script>