<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>

  <body>
    <canvas id="bg" width="500" height="500"></canvas>
    <canvas id="fg" width="500" height="500"></canvas>
    
    <div class="controls">
      <form>
        <fieldset>
          <legend>Shape</legend>
          <label><input type="radio" name="shape" value="triangle"> Triangle</label>
          <label><input type="radio" name="shape" value="square"> Square</label>
        </fieldset>
        <label>
          Shape Size: <input name="size" type="number" min="3" max="20">px
        </label>
      </form>
    </div>
  </body>

</html>
// Size of each side of the shape in pixels.
var size = 8;

// Height of the shape in pixels;
var height = 0;

// Either 'triangle' or 'square';
var shape = 'triangle';

// Handles to DOM elements.
var bg, fg, form, shapeField, sizeField;
    
// How far the foreground has been rotates in degrees;
var degrees = 0;

// Whether the mask is auto-animating
var animating = false;

// Set the canvas to fill the screen.
function resizeCanvas(el) {
  
  if (window.innerWidth > window.innerHeight) {
    el.width = el.height = window.innerWidth;
  } else {
    el.width = el.height = window.innerHeight;
  }
  
  el.style.marginTop = '-' + (el.height / 2) + 'px';
  el.style.marginLeft = '-' + (el.width / 2) + 'px';
  
  fillShape(el);
}

// Draws a single shape to a drawing context at a given coordinate.
function drawShape(ctx, x, y) {
  ctx.beginPath();

  ctx.moveTo(x, y);
  ctx.lineTo(x + size, y);
  
  if (shape === 'triangle') {
    ctx.lineTo(x + (size / 2), y - height);
  } else {
    ctx.lineTo(x + size, y + size);
    ctx.lineTo(x, y + size);
  }
  
  ctx.fill();
}

// Fills the canvas with a particular shape.
function fillShape(canvas) {
  var ctx = canvas.getContext('2d');
      
  var shift = false;
  var vskip, hskip;
  
  if (shape === 'triangle') {
    vskip = size;
    hskip = size / 2;
  } else {
    vskip = size * 2;
    hskip = size;
  }
  
  for (var j = 0; j < canvas.height + size; j += size) {
    for (var i = 0; i < canvas.width + size; i += vskip) {
      if (shift) {
        drawShape(ctx, i - hskip, j);
      } else {
        drawShape(ctx, i, j);
      }
    }
    shift = !shift;
  }
}

function setRotation(el, deg) {
  el.style.transform = 'rotateZ(' + deg + 'deg)';
}

function animate(el) {
  var lastTimestamp = performance.now();

  function doAnimate(timestamp) {
    if (!animating) {
      return;
    }

    setRotation(el, degrees);

    degrees += (timestamp - lastTimestamp) / 50;
    lastTimestamp = timestamp;

    requestAnimationFrame(doAnimate);
  }
  doAnimate(performance.now());
}

function onResize() {
  if (shape === 'triangle') {
    height = (Math.sqrt(3) / 2) * size;
  } else {
    height = size;
  }
  resizeCanvas(bg);
  resizeCanvas(fg);
}

function onClick() {
  animating = !animating;
  if (animating) {
    animate(fg);
  }
}

function onMove(evt) {
  if (animating) {
    return;
  }
  var cx  = window.innerWidth / 2,
      cy  = window.innerHeight / 2,
      rad = Math.atan2(evt.clientY - cy, evt.clientX - cx);
      
  degrees = rad * 180 / Math.PI;
  setRotation(fg, degrees);
}

function onInput() {
  size = parseInt(sizeField.value, 10);
  shape = shapeField.value;
  onResize();
}

function main() {
  bg = document.getElementById('bg');
  fg = document.getElementById('fg');
  form = document.forms[0];
  sizeField = form.elements.size;
  shapeField = form.elements.shape;
  
  var bgCtx = bg.getContext('2d'),
      fgCtx = fg.getContext('2d');
  
  bgCtx.strokeStyle = bgCtx.fillStyle = 
    fgCtx.strokeStyle = fgCtx.fillStyle = 'black';
  
  onResize();
  sizeField.value = size;
  shapeField.value = shape;
  
  // Event Listners
  
  window.addEventListener('resize', onResize);
  bg.addEventListener('click', onClick);
  fg.addEventListener('click', onClick);
  document.addEventListener('mousemove', onMove);
  document.addEventListener('touchmove', function(evt) {
    onMove(evt.touches.item(0));
  });
  document.forms[0].addEventListener('submit', function (evt) {
    evt.preventDefault();
  });
  sizeField.addEventListener('input', onInput);
  
  for (var i = 0; i < shapeField.length; i++) {
    shapeField.item(i).addEventListener('click', onInput);
  }
}

// Wait for the page to be finished loading
if (document.readyState !== 'loading') {
  main();
} else {
  document.addEventListener('DOMContentLoaded', main);
}
body {
  background: white;
  overflow: hidden;
}

canvas {
  position: fixed;
  top: 50%;
  left: 50%;
}

.controls {
  background-color: lightblue;
  position: fixed;
  z-index: 10;
}

#sizeField {
  width: 50px;
}
Inspired by: https://www.reddit.com/r/math/comments/41kgud/moir%C3%A9_pattern/

Click to toggle animation.