live - 60fps 1920 x 1080
<!DOCTYPE html>
<html>
<head>
<title>Spirograph</title>
<style>
:root { --bg-color: #06040f; --trace-1: #f472b6; --trace-2: #22d3ee; }
* { margin: 0; padding: 0; }
body { height: 100vh; background: var(--bg-color); overflow: hidden; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const cv = document.getElementById('c'), ctx = cv.getContext('2d');
const css = getComputedStyle(document.documentElement);
const bg = (css.getPropertyValue('--bg-color') || '#06040f').trim();
function hex(h){h=(h||'').trim().replace('#','');if(h.length===3)h=h.split('').map(c=>c+c).join('');const n=parseInt(h,16);return[(n>>16)&255,(n>>8)&255,n&255];}
const t1 = hex(css.getPropertyValue('--trace-1') || '#f472b6');
const t2 = hex(css.getPropertyValue('--trace-2') || '#22d3ee');
let w, h, cx, cy, S;
function build() { w = cv.width = innerWidth; h = cv.height = innerHeight; cx = w / 2; cy = h / 2; S = Math.min(w, h) * 0.42; }
addEventListener('resize', build); build();
function step(t) {
ctx.fillStyle = bg; ctx.fillRect(0, 0, w, h);
const R = 1, r = 0.28 + 0.16 * Math.sin(t * 0.3), d = 0.7 + 0.25 * Math.sin(t * 0.21);
const k = (R - r) / r, rot = t * 0.15;
const steps = 2600, span = Math.PI * 2 * 14;
ctx.lineWidth = 1.2; ctx.globalCompositeOperation = 'lighter';
ctx.beginPath();
for (let i = 0; i <= steps; i++) {
const th = i / steps * span;
let x = (R - r) * Math.cos(th) + d * Math.cos(k * th);
let y = (R - r) * Math.sin(th) - d * Math.sin(k * th);
const xr = x * Math.cos(rot) - y * Math.sin(rot), yr = x * Math.sin(rot) + y * Math.cos(rot);
const px = cx + xr * S, py = cy + yr * S;
if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py);
}
const mix = 0.5 + 0.5 * Math.sin(t * 0.4);
ctx.strokeStyle = `rgba(${t1[0]+(t2[0]-t1[0])*mix|0},${t1[1]+(t2[1]-t1[1])*mix|0},${t1[2]+(t2[2]-t1[2])*mix|0},0.85)`;
ctx.stroke();
ctx.globalCompositeOperation = 'source-over';
}
if (matchMedia('(prefers-reduced-motion: reduce)').matches) step(1.0);
else { let t = 0; (function loop(){ t += 0.01; step(t); requestAnimationFrame(loop); })(); }
</script>
</body>
</html> About this animation
A single luminous curve loops endlessly through a morphing spirograph, tracing hypnotic geometric flowers that never quite repeat.
Related