>_htmlhub

Flocking Boids

Live previewPerformance varies by device
flocking-boids.html

No account needed to copy or download. Check dependencies and thelicense status before publishing.

<!DOCTYPE html>
<html>
<head>
<title>Flocking Boids</title>
<style>
    :root { --bg-color: #060a14; --boid-1: #38bdf8; --boid-2: #c084fc; }
    * { 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') || '#060a14').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 c1 = hex(css.getPropertyValue('--boid-1') || '#38bdf8');
    const c2 = hex(css.getPropertyValue('--boid-2') || '#c084fc');
    let w, h; const N = 140, boids = [];
    const mouse = { x: -1e4, y: -1e4 };
    function build() {
        w = cv.width = innerWidth; h = cv.height = innerHeight;
        boids.length = 0;
        for (let i = 0; i < N; i++) boids.push({ x: Math.random() * w, y: Math.random() * h, vx: (Math.random() - 0.5) * 2, vy: (Math.random() - 0.5) * 2 });
    }
    addEventListener('resize', build);
    addEventListener('mousemove', e => { mouse.x = e.clientX; mouse.y = e.clientY; });
    addEventListener('mouseleave', () => { mouse.x = mouse.y = -1e4; });
    build();
    const R = 60, R2 = R * R, SEP = 24 * 24, MAX = 3.4;
    function step() {
        ctx.fillStyle = bg; ctx.fillRect(0, 0, w, h);
        for (const b of boids) {
            let ax = 0, ay = 0, cxs = 0, cys = 0, sx = 0, sy = 0, n = 0;
            for (const o of boids) {
                if (o === b) continue;
                const dx = o.x - b.x, dy = o.y - b.y, d2 = dx * dx + dy * dy;
                if (d2 < R2) {
                    ax += o.vx; ay += o.vy; cxs += o.x; cys += o.y; n++;
                    if (d2 < SEP) { sx -= dx; sy -= dy; }
                }
            }
            if (n > 0) {
                b.vx += (ax / n - b.vx) * 0.05 + (cxs / n - b.x) * 0.0009;
                b.vy += (ay / n - b.vy) * 0.05 + (cys / n - b.y) * 0.0009;
            }
            b.vx += sx * 0.008; b.vy += sy * 0.008;
            // flee cursor
            const mdx = b.x - mouse.x, mdy = b.y - mouse.y, md2 = mdx * mdx + mdy * mdy;
            if (md2 < 140 * 140) { b.vx += mdx / (md2 + 40) * 90; b.vy += mdy / (md2 + 40) * 90; }
            const sp = Math.hypot(b.vx, b.vy); if (sp > MAX) { b.vx = b.vx / sp * MAX; b.vy = b.vy / sp * MAX; }
            b.x += b.vx; b.y += b.vy;
            if (b.x < 0) b.x += w; else if (b.x > w) b.x -= w;
            if (b.y < 0) b.y += h; else if (b.y > h) b.y -= h;
            const sp2 = Math.min(1, sp / MAX);
            ctx.fillStyle = `rgb(${c1[0]+(c2[0]-c1[0])*sp2|0},${c1[1]+(c2[1]-c1[1])*sp2|0},${c1[2]+(c2[2]-c1[2])*sp2|0})`;
            const a = Math.atan2(b.vy, b.vx);
            ctx.beginPath();
            ctx.moveTo(b.x + Math.cos(a) * 6, b.y + Math.sin(a) * 6);
            ctx.lineTo(b.x + Math.cos(a + 2.5) * 4, b.y + Math.sin(a + 2.5) * 4);
            ctx.lineTo(b.x + Math.cos(a - 2.5) * 4, b.y + Math.sin(a - 2.5) * 4);
            ctx.closePath(); ctx.fill();
        }
    }
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) { for (let i = 0; i < 60; i++) step(); }
    else (function loop() { step(); requestAnimationFrame(loop); })();
</script>
</body>
</html>

About this animation

Hundreds of boids swarm with lifelike flocking — separating, aligning, and drawing together into murmurations that scatter from your cursor.

Under the hood

Canvas Reynolds Boids Flocking

A classic Reynolds boids simulation on Canvas 2D: 140 boids each steer from neighbours within 60px, aligning velocity by 0.05, cohering toward the neighbour centre by 0.0009, and separating within 24px. Speed is capped at 3.4, boids wrap at the edges and are drawn as small triangles oriented with Math.atan2(), and boids within 140px flee the mouse position.

Source Code
Copied to clipboard