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>