Dithered SVG Particles
Any SVG turned into a field of 10k+ interactive particles. Hover to push them apart, click to fire a shockwave. 2D canvas, no WebGL.
An experiment in porting Emil Kowalski's dithered-canvas technique to work with any SVG. The mark loads, rasterizes, and becomes a cloud of responsive dots — no shaders, no libraries, just typed arrays and bucketed 2D canvas draws.
How it works
- The SVG loads as an
<img>and rasterizes into a 256×256 offscreen canvas. - Every pixel becomes a particle; RGB is quantized to 4-bit bins.
- Dots with the same bin collapse into one color bucket (~20–80 per SVG).
- Draw pass: one
fillStyleper bucket, batchedfillRects. 10k+ particles at 60fps. - Hover pushes with cubic falloff; click fires an expanding shockwave;
requestAnimationFramestops at rest.
The craft details
- Pre-allocated
Float32Array/Int32Arrayfor all per-dot state — no per-frame allocation, no GC pressure. - Exponential smoothing at 12%/frame (
disp += (target - disp) × 0.12) gives spring-adjacent feel for a fraction of the cost of real physics. - Epsilon snap (
Math.abs(disp) < 0.01 → 0) is what lets the animation loop actually end — without it, dots drift in sub-pixel floats forever and burn CPU indefinitely. - Pointer filtering — only
pointerType === 'mouse'triggers the hover loop. Touch devices skip the expensive per-frame repulsion but still get click shockwaves. data-readyattribute flips after the first paint, triggering a CSS fade-in. Avoids the flash of blank canvas on mount.
The whole component is ~300 lines. The technique scales: a four-shape brand mark, an illustrated portrait, or an icon — if you can render it as an SVG, the particle field picks up the colors automatically.