Hey folks, I’m cleaning up a UI micro-interaction at work and I’m trying to keep the bundle lean, so I replaced a small animation lib with a tiny spring. The failure mode I hit before was accidental double-imports from different packages, and suddenly the “small” easing code costs more than the feature.
// dependency-free rAF spring with optional stagger
export function springTo({
from = 0,
to = 1,
stiffness = 240,
damping = 26,
mass = 1,
dtMax = 1 / 30,
staggerMs = 0,
onUpdate,
onDone,
}) {
let x = from;
let v = 0;
let raf = 0;
let last = 0;
const startAt = performance.now() + staggerMs;
function step(t) {
raf = requestAnimationFrame(step);
if (t < startAt) return;
if (!last) last = t;
let dt = (t - last) / 1000;
last = t;
if (dt > dtMax) dt = dtMax;
const F = -stiffness * (x - to);
const a = (F - damping * v) / mass;
v += a * dt;
x += v * dt;
onUpdate?.(x);
if (Math.abs(v) < 0.001 && Math.abs(x - to) < 0.001) {
cancelAnimationFrame(raf);
onUpdate?.(to);
onDone?.();
}
}
raf = requestAnimationFrame(step);
return () => cancelAnimationFrame(raf);
}
Neat part is it feels like a real spring and I can do a cheap stagger just by passing different staggerMs, without pulling any deps into the build.