Snippet

Shape drawing

Full page view

Description

Draw patterns with shapes (mouse only).

Created on 21 June 2026.

Code

<!-- 
  TODO:
  - Use Pointer Events instead of mouse
-->

<style>
  body {
    font-family: ui-sans-serif, system-ui, sans-serif;
  }

  canvas {
    position: fixed;
    inset: 0;
  }

  .controls {
    position: fixed;
  }
</style>

<canvas></canvas>

<div class='controls'>
  <label>Select color: <input class='control' id='color' type='color'></label>
  <br>
  <label><input class='control' id='rainbow' type='checkbox' checked> Rainbow</label>
  <br>
  <a class='control' id='download' download='shape-drawing.png'>Download drawing</a>
</div>

<script>
  const canvas = document.querySelector('canvas'),
    ctx = canvas.getContext('2d'),
    controls = [ ...document.querySelectorAll('.control') ]
      .reduce((list, control) => ({
        [control.id]: control, ...list 
      }), {})
    

  function resizeCanvas() {
    canvas.width = innerWidth;
    canvas.height = innerHeight;
  }

  resizeCanvas();

  let shape = 'circle',
    hue = 0,
    radius = 40,
    states = [];

  const draw = (ev) => {
    if (controls.rainbow.checked) {
      ctx.strokeStyle = `hsl(${hue}deg 50 50)`;
      hue++;
    } else {
      ctx.strokeStyle = controls.color.value;
    }

    switch (shape) {
      case 'circle':
        ctx.beginPath();
        ctx.moveTo(ev.x + radius, ev.y);
        ctx.arc(ev.x, ev.y, radius, 0, 2 * Math.PI);
        ctx.stroke();
        break;
      case 'triangle':
        ctx.beginPath();
        ctx.moveTo(ev.x, ev.y + radius);
        const angle = Math.PI * 2 / 3;
        ctx.lineTo(ev.x + Math.sin(angle) * radius, ev.y + Math.cos(angle) * radius);
        ctx.lineTo(ev.x + Math.sin(angle * 2) * radius, ev.y + Math.cos(angle * 2) * radius);
        ctx.closePath()
        ctx.stroke();
        break;
      case 'square':
        ctx.strokeRect(ev.x - radius, ev.y - radius, radius * 2, radius * 2)
    }
  }

  window.addEventListener('resize', resizeCanvas);

  canvas.addEventListener('mousedown', (ev) => {
    window.addEventListener('mousemove', draw);
  });
  window.addEventListener('mouseup', (ev) => {
    window.removeEventListener('mousemove', draw);
    states.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
    canvas.toBlob((blob) => {
      controls.download.href = URL.createObjectURL(blob);
    })
  });

  window.onkeydown = (ev) => {
    switch (ev.key) {
      case 'c':
        shape = 'circle';
        break;
      case 't':
        shape = 'triangle';
        break;
      case 's':
        shape = 'square';
        break;
      case 'ArrowUp':
        radius += 5;
        break;
      case 'ArrowDown':
        if (radius !== 5)
          radius -= 5;
        break;
      case 'z':
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        if (states.length) ctx.putImageData(states.pop(), 0, 0);
        break;
    }
  }
</script>