library / backgrounds / wave-interference
live - 60fps 1920 x 1080
wave-interference.html - self-contained
<!DOCTYPE html>
<html>
<head>
<title>Wave Interference</title>
<style>
    :root { --bg-color: #050912; --wave-1: #0ea5e9; --wave-2: #f472b6; }
    * { margin: 0; padding: 0; }
    body { height: 100vh; background: var(--bg-color); overflow: hidden; }
    canvas { display: block; width: 100vw; height: 100vh; }
</style>
</head>
<body>
    <canvas id="c"></canvas>
<script>
    const cv = document.getElementById('c'), ctx = cv.getContext('2d');
    const css = getComputedStyle(document.documentElement);
    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 c1 = hex(css.getPropertyValue('--wave-1') || '#0ea5e9');
    const c2 = hex(css.getPropertyValue('--wave-2') || '#f472b6');
    const SCALE = 5; let lw, lh, img, buf;
    const src = [];
    function build() {
        cv.width = Math.ceil(innerWidth / SCALE); cv.height = Math.ceil(innerHeight / SCALE);
        lw = cv.width; lh = cv.height; img = ctx.createImageData(lw, lh); buf = img.data;
        ctx.imageSmoothingEnabled = true;
        src.length = 0;
        for (let i = 0; i < 5; i++) src.push({ x: Math.random() * lw, y: Math.random() * lh, ax: Math.random() * 6, ay: Math.random() * 6, sx: 0.15 + Math.random() * 0.2, sy: 0.15 + Math.random() * 0.2 });
    }
    addEventListener('resize', build); build();
    function step(t) {
        for (const s of src) { s.px = lw / 2 + Math.cos(s.ax + t * s.sx) * lw * 0.4; s.py = lh / 2 + Math.sin(s.ay + t * s.sy) * lh * 0.4; }
        for (let y = 0; y < lh; y++) {
            for (let x = 0; x < lw; x++) {
                let v = 0;
                for (const s of src) { const d = Math.hypot(x - s.px, y - s.py); v += Math.cos(d * 0.28 - t * 2.4); }
                const n = 0.5 + v / src.length * 0.5;   // 0..1
                const i = (y * lw + x) * 4;
                buf[i] = c1[0] + (c2[0] - c1[0]) * n; buf[i + 1] = c1[1] + (c2[1] - c1[1]) * n; buf[i + 2] = c1[2] + (c2[2] - c1[2]) * n; buf[i + 3] = 255;
            }
        }
        ctx.putImageData(img, 0, 0);
    }
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) step(1.0);
    else { let t = 0; (function loop(){ t += 0.02; step(t); requestAnimationFrame(loop); })(); }
</script>
</body>
</html>

About this animation

Concentric ripples radiate from wandering sources and overlap into shifting moire interference — like raindrops on a pond of light.

Source Code
Copied to clipboard