Skip to content
Performance

Why 60fps feels like 24fps

Smoothness is not frame rate — it is frame pacing. A steady 24 feels calm; a stuttery 60 feels broken. What jank actually is, why the eye forgives slow but not uneven, and how to measure the difference.

Intermediate2 min read (computed · recorded 11)updated 2026-09-10performancefpsjankbrowser

The eye forgives slow. It does not forgive uneven.

Film has run at 24 frames per second for a century and nobody calls it janky, because every frame arrives exactly when expected — 41.7ms apart, no exceptions. A web animation at a steady 30fps reads as smooth but slightly dreamy. The same animation averaging 30fps with frames arriving at 8ms, 40ms, 12ms, 55ms reads as broken — because the brain does not perceive frame rate, it perceives *gaps*.

Jank is not 'low fps'. Jank is a frame that arrives late, especially after a run of on-time frames. The eye has a rhythm detector, and a single late frame is a skipped heartbeat.

The useful metric: longest frame, not average

DevTools performance traces show a bar per frame; the ones over 16.7ms that cluster are your jank. The average can say 55fps while one 90ms frame breaks the animation's spine. Hunt the worst frame, not the mean.

Where the gaps come from

  • Main-thread work: layout and paint are the usual suspects — animating width or top forces layout every frame, and layout takes whatever time the DOM gives it.
  • Compositor interruptions: a layer is promoted mid-animation, or a new layer appears (a dropdown opening under the animation) and the GPU re-composites.
  • Garbage collection: a rAF loop that allocates objects every frame (strings, arrays) makes the GC pause the main thread unpredictably.
  • Background tabs waking up, extension work, or a font swap that triggers a reflow mid-sequence — the classic invisible jank.
  • Scroll-handler pileup: a scroll listener that writes layout (reads offsetTop, then writes style) forces synchronous reflow on every scroll event.
frame-budget.md
16.7ms  total budget at 60fps (one frame)
 8ms    style + layout          <- keep tiny
 3ms    paint
 4ms    compositor
 1ms    JavaScript
-----
Keep JS under 4-5ms per frame and the
rest of the budget survives real devices.

Why the fix is usually fewer moving parts

The cheapest way to a steady 60 is to animate only transform and opacity (compositor-only, no layout, no paint) and to promote long-running layers explicitly with will-change. But the deeper fix is humility: a page where four things animate at once will fight for the budget forever. Cut the animation to the one that tells the story, and steady frames come back — because the fastest frame is the one you did not ask for.

Practise the lesson

Theory sticks when you ship it. These original Motif assets put this guide's lesson to work — open one and copy it into your own page.