Dithered SVG Particles
Jedes SVG wird zu einem Feld aus über 10k interaktiven Partikeln. Hover drückt sie auseinander, Klick feuert eine Schockwelle. 2D Canvas, kein WebGL.
Lokalisierte Projektversion mit bewahrtem Stack, Links, Code und zentralen technischen Details.
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.