library / interactive / dot-grid-spotlight
live - 60fps 1920 x 1080
dot-grid-spotlight.html - self-contained
<!DOCTYPE html>
<html>
<head>
<title>Dot Grid Spotlight</title>
<style>
    :root {
        --bg-color: #0a0a0f;
        --dot-color: #2a2a3a;
        --glow-color: #6366f1;
    }
    * { 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 canvas = document.getElementById('c');
    const ctx = canvas.getContext('2d');
    // Read customizable colors from CSS variables
    const css = getComputedStyle(document.documentElement);
    const dotColor = css.getPropertyValue('--dot-color').trim() || '#2a2a3a';
    const glowColor = css.getPropertyValue('--glow-color').trim() || '#6366f1';
    function hexToRgb(h) {
        h = h.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 glow = hexToRgb(glowColor);
    let w, h, dots, mouse = { x: -9999, y: -9999 }, hasMouse = false;
    const GAP = 34, RADIUS = 1.6, SPOTLIGHT = 160;
    function build() {
        w = canvas.width = window.innerWidth;
        h = canvas.height = window.innerHeight;
        dots = [];
        const cols = Math.ceil(w / GAP) + 1;
        const rows = Math.ceil(h / GAP) + 1;
        for (let i = 0; i < cols; i++)
            for (let j = 0; j < rows; j++)
                dots.push({ x: i * GAP, y: j * GAP });
    }
    window.addEventListener('mousemove', (e) => { mouse.x = e.clientX; mouse.y = e.clientY; hasMouse = true; });
    window.addEventListener('mouseleave', () => { hasMouse = false; });
    window.addEventListener('resize', build);
    build();
    let t = 0;
    function draw() {
        t += 0.006;
        ctx.clearRect(0, 0, w, h);
        // When there's no cursor, drift the spotlight so previews stay alive
        let mx = mouse.x, my = mouse.y;
        if (!hasMouse) {
            mx = w * (0.5 + 0.32 * Math.cos(t));
            my = h * (0.5 + 0.28 * Math.sin(t * 1.3));
        }
        for (const d of dots) {
            const dx = d.x - mx, dy = d.y - my;
            const dist = Math.sqrt(dx * dx + dy * dy);
            const inten = Math.max(0, 1 - dist / SPOTLIGHT);
            if (inten > 0.01) {
                ctx.beginPath();
                ctx.arc(d.x, d.y, RADIUS + inten * 2.2, 0, Math.PI * 2);
                ctx.fillStyle = `rgba(${glow[0]},${glow[1]},${glow[2]},${0.15 + inten * 0.85})`;
                ctx.fill();
            } else {
                ctx.beginPath();
                ctx.arc(d.x, d.y, RADIUS, 0, Math.PI * 2);
                ctx.fillStyle = dotColor;
                ctx.fill();
            }
        }
        requestAnimationFrame(draw);
    }
    draw();
</script>
</body>
</html>

About this animation

A subtle dot grid that lights up around your cursor with a glowing spotlight — the clean, tactile background popularized by Linear and shadcn.

Source Code
Copied to clipboard