From video to dots: rebuilding the homepage background

The homepage used to have a looping video background — a pre-rendered animation that autoplayed behind the hero section. It looked fine, but the file was heavy (13 MB), it couldn’t respond to user input, and it always looked slightly soft because the video resolution never quite matched the viewport.

I wanted to replace it with something lighter and more interactive — a grid of colored dots that pulse, shimmer, and react to the mouse. No video file to load, no seeking glitches, and it could run at any resolution without looking pixelated. I built the whole thing with Claude Code, Anthropic’s coding agent, iterating on the animation logic through conversation.

Canvas2D: the first prototype

The first version used the Canvas2D API. Every frame, the script loops over every dot, computes its color from a triangle-wave oscillation, applies brightness caps and mouse proximity effects, then draws an arc. It works — the dots pulse in cyan and blue, waves sweep across, and the mouse lights up nearby dots.

The problem is scaling. Each dot requires a JavaScript function call plus a canvas arc() + fill() per frame. At 20 dots across (like the demo below), it’s trivial. At 180 dots across for a full-viewport background, you’re drawing 10,000+ arcs per frame entirely on the CPU.

WebGL: moving the math to the GPU

The fix is to stop doing per-dot work in JavaScript. In the WebGL version, all the dot properties (position, phase, speed, color, brightness caps) are uploaded once as vertex attributes. Each frame, the CPU only sets a handful of uniforms — time, mouse position, wave parameters — and the GPU runs the oscillation, capping, and wave logic for every dot in parallel inside a vertex shader.

The fragment shader just discards pixels outside a circle (making the square point into a round dot) and outputs the color computed by the vertex shader. The result is the same visual effect, but the frame time barely changes whether you have 60 dots or 20,000.

The difference at scale

With 20 dots per row like these demos, both approaches run effortlessly. The real gap shows up at full viewport density — 180 columns, 10,000+ dots. The Canvas2D version starts dropping frames on anything but a fast desktop, while the WebGL version stays at 60fps because the GPU is doing the heavy lifting. Same visual effect, fundamentally different performance ceiling.

Maximilian JLJ Fürst
Maximilian JLJ Fürst
Assistant Professor of Computational Protein Design

I research computational protein design and high-throughput protein engineering.