library / backgrounds / voronoi-shatter
live - 60fps 1920 x 1080
voronoi-shatter.html - self-contained
<!DOCTYPE html>
<html>
<head>
<title>Voronoi Shatter</title>
<style>
    :root {
        --bg-color: #060612;
        --shard-1: #6366f1;
        --shard-2: #ec4899;
        --shard-3: #22d3ee;
        --crack-color: #05050a;
    }
    * { margin: 0; padding: 0; }
    body { height: 100vh; background: var(--bg-color); overflow: hidden; }
    canvas { display: block; width: 100vw; height: 100vh; image-rendering: pixelated; }
</style>
</head>
<body>
    <canvas id="c"></canvas>
<script>
    const cv = document.getElementById('c');
    const ctx = cv.getContext('2d');
    ctx.imageSmoothingEnabled = false;
    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 pal = [
        hex(css.getPropertyValue('--shard-1') || '#6366f1'),
        hex(css.getPropertyValue('--shard-2') || '#ec4899'),
        hex(css.getPropertyValue('--shard-3') || '#22d3ee')
    ];
    const crack = hex(css.getPropertyValue('--crack-color') || '#05050a');
    const SCALE = 5;
    const SEEDS = 18;
    let lw, lh, img, buf, seeds;
    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;
        seeds = [];
        for (let i = 0; i < SEEDS; i++) {
            const c = pal[i % pal.length];
            seeds.push({
                x: Math.random() * lw, y: Math.random() * lh,
                vx: (Math.random() - 0.5) * 0.35, vy: (Math.random() - 0.5) * 0.35,
                c
            });
        }
    }
    addEventListener('resize', build);
    build();
    function step() {
        for (const s of seeds) {
            s.x += s.vx; s.y += s.vy;
            if (s.x < 0 || s.x > lw) s.vx *= -1;
            if (s.y < 0 || s.y > lh) s.vy *= -1;
        }
        for (let y = 0; y < lh; y++) {
            for (let x = 0; x < lw; x++) {
                let d1 = 1e9, d2 = 1e9, best = seeds[0];
                for (const s of seeds) {
                    const dx = x - s.x, dy = y - s.y, d = dx * dx + dy * dy;
                    if (d < d1) { d2 = d1; d1 = d; best = s; }
                    else if (d < d2) { d2 = d; }
                }
                const edge = Math.sqrt(d2) - Math.sqrt(d1); // small near cell boundary
                const i = (y * lw + x) * 4;
                if (edge < 1.1) {
                    buf[i] = crack[0]; buf[i + 1] = crack[1]; buf[i + 2] = crack[2];
                } else {
                    // subtle radial shading inside each shard for a glassy look
                    const shade = 0.55 + 0.45 * Math.max(0, 1 - Math.sqrt(d1) / 42);
                    buf[i] = best.c[0] * shade; buf[i + 1] = best.c[1] * shade; buf[i + 2] = best.c[2] * shade;
                }
                buf[i + 3] = 255;
            }
        }
        ctx.putImageData(img, 0, 0);
    }
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
        step();
    } else {
        (function loop() { step(); requestAnimationFrame(loop); })();
    }
</script>
</body>
</html>

About this animation

Glowing stained-glass cells drift and reshape like slowly cracking ice, each glassy shard shaded from its core with dark fracture lines between.

Source Code
Copied to clipboard