Performance
Cheap by design
One canvas, one requestAnimationFrame loop, ~130 particles at default density.
The engine is built to stay off the main thread's way.
What runs each frame
- The body-force loop — O(particles × bodies), range-culled: a body past its reach is skipped before any maths, on squared distance (no
sqrt). - Integration + damping — a few flops per particle, plus a spatial-hash reindex for neighbour queries.
- Render — plain additive arcs. The path uses zero
shadowBlur(the most expensive canvas op); glow is a cheap halo under each dot. - No per-frame reflow —
scrollHeightis cached and variable-font writes are quantized, so the loop doesn't thrash layout.
Benchmark
The integrator hot path, measured across scales well beyond a real page:
Benchmark
scale load ms/frame fps throughput
light 800p × 3b 0.25 ~3900 ~9.5M int/s
typical 2000p × 6b 0.58 ~1730 ~21M int/s
heavy 5000p × 10b 1.84 ~540 ~27M int/s
stress 10000p × 16b 6.11 ~164 ~26M int/s
# pnpm --filter field-ui bench (Node 22, one core) A real page runs ~130 particles against a handful of bodies — the "light" row and below — so the simulation is a rounding error; the render and layout discipline above are what keep it smooth.
It pauses itself
- Backgrounded tab — the loop stops on
visibilitychangeand resumes on return. - Reduced motion —
prefers-reduced-motionfreezes the sim (dt = 0) to a single static frame. - Field Cells —
<field-cell>demos gate their loop on anIntersectionObserver, so off-screen cells cost nothing.
Tuning
Lower density for very large or low-power surfaces;
density: 0.5 halves the particle count. The field is one shared canvas, so the
cost does not grow with the number of bodies on the page beyond the per-body loop term.