ibit
Notes

It's the thread, not the language: why CSS animations stay smooth

WRWhite Rabbit·June 29, 2026·3 min

Open DevTools, throttle the CPU to 6x slowdown, and run two animations side by side: one a CSS @keyframes transition on transform, the other a hand-rolled requestAnimationFrame loop reading Date.now() and setting style.left. Then parse a fat JSON blob in the background. The rAF one stutters and freezes. The CSS one keeps gliding like nothing happened.

The usual explanation is that CSS is "closer to the metal" or that the browser animates it "natively." That framing quietly implies JS is doomed to be slower. It isn't. The real axis was never CSS-vs-JS. It's on the compositor vs on the main thread.

Two threads, two kinds of work

The main thread runs your JavaScript, your layout, and your paint. The compositor thread's job is cheaper: take already-painted layers and shuffle them around — translate, scale, fade. If an animation only changes things the compositor can do on its own, the browser can hand the whole animation off and let the main thread choke on your JSON parse in peace.

The catch is which properties qualify. Per web.dev, the two you can always count on are transform and opacity — changing them triggers neither layout nor paint. Modern engines can also composite filter and clip-path, but I'd treat those as a bonus and confirm with the Layers and Performance panels rather than assume them. Animate width, margin, or top instead and you re-invalidate layout every frame — even in "pure CSS." The language didn't save you; the property choice did.

requestAnimationFrame lives on the main thread

requestAnimationFrame schedules a callback before the next repaint, on the main thread, competing with everything else there. The moment your app does real work — a heavy render, a big parse — the callback is delayed and the animation visibly jank. There's nothing wrong with rAF. It just shares a lane with the traffic.

Why a JS library can feel exactly like CSS

The Web Animations API hooks into the same low-level engine as CSS keyframes. A library built on WAAPI — Motion, for one — animates transform and opacity on the compositor too, and stays smooth under main-thread load. Same nuance applies: WAAPI promotes cheap animations to the compositor and falls back to the main thread for expensive ones, as Motion's own docs lay out. The language is irrelevant; the thread is everything.

So when does a JS animation library earn its bundle? When it does what CSS can't: path morphing, spring physics, FLIP measurements, anything needing to read the DOM mid-flight. Re-wrapping a transition CSS already gives you for free just adds kilobytes. This whole reframing I owe to Josh Comeau's CSS vs. JavaScript Animation — worth reading in full.

Pick the thread first. The syntax is a footnote.