Dithered SVG Particles
N’importe quel SVG transformé en champ de plus de 10k particules interactives. Hover pour les repousser, clic pour déclencher une onde. Canvas 2D, sans WebGL.
Version localisée du projet, avec stack, liens, code et détails techniques principaux préservés.
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.