Why smooth animation is hard
Animations, transitions, and small UI effects should make an app feel responsive, even "native." They can just as easily make it feel clunky. The difference is usually jank — animation hitches where frames are dropped or delayed.
The core problem is v-sync. Displays refresh on a regular interval, typically around 60 times per second. New frames must be generated between these refreshes, not during them; otherwise the screen tears, showing part of one frame and part of another. To keep an animation smooth, the browser has to have a new frame ready every time the screen refreshes. That leaves roughly 16ms on a 60Hz display to run all JavaScript, perform layout, paint, and deliver the frame — and the work for the next frame should ideally start the moment the previous one is displayed.
Timing matters more than FPS
Many developers drive animations with setInterval or setTimeout at 16ms intervals. That approach has two fundamental problems. JavaScript timer resolution is only accurate to a few milliseconds, and devices have different refresh rates: 60Hz is common, but some phones run at 59Hz, some laptops drop to 50Hz in low-power mode, and some desktop monitors hit 70Hz. With a fixed timer interval, you will slowly drift out of sync with the display and periodically miss the window for a frame. Even occasional misses — a dropped frame every second or two — are noticeable, because the eye is far more sensitive to irregular timing than to a lower average framerate. You also waste CPU and power on frames that are never displayed.
requestAnimationFrame solves the timing problem by asking the browser to invoke your callback when it is about to produce a frame, regardless of the refresh rate. It has additional benefits: animations in background tabs are paused to conserve resources, and if the system cannot keep up with the display, the browser can throttle the callback to a consistent rate (for example, 30 times per second on a 60Hz screen). A steady 30Hz animation looks better than a 60Hz animation that misses frames irregularly.
Working within the frame budget
With a 16ms frame budget, any JavaScript inside a requestAnimationFrame callback that runs longer than that makes it impossible to hit v-sync. Chrome's DevTools timeline can help track down budget overruns. Recording an animation and switching the timeline to "Frames" view shows exactly how much time each frame takes.
A requestAnimationFrame callback that runs for hundreds of milliseconds is an order of magnitude over budget. Drilling into one of those long callbacks reveals the cause — in the example in the original material, it was a lot of layout work triggered by reading scrollTop. The fix is to inspect the callback body and find the specific operation causing the relayout, then restructure to avoid it. When frames come in at roughly 16ms with visible gaps between them, that headroom is what allows the browser to do background work without dropping frames.
What else causes jank
Even a lean requestAnimationFrame callback that executes in a few milliseconds can be stalled when other work is handled by the main thread. Incoming XHR processing, input event handlers, or scheduled timer updates can run for unpredictable lengths of time without yielding — on mobile devices, sometimes for hundreds of milliseconds, during which animation stops completely. Those pauses are jank.
There is no single fix, but a few architectural habits help:
- Keep input handlers light. Running lengthy JavaScript or rearranging the whole page inside an
onscrollhandler is a common source of severe jank. - Move long-running processing into
requestAnimationFramecallbacks or Web Workers. - If work must go into the callback, chunk it so each frame processes only a small piece, or defer it until after the important animation finishes. Short callbacks let animation continue smoothly.
CSS animations as an alternative
The best way to keep JavaScript out of the way is to not use it for animation at all. CSS animations can often run on Chrome for Android even while JavaScript is executing — a useful property because the browser typically does one thing at a time: running JS, layout, or paint, but never two simultaneously. That assumption holds in the DevTools timeline view, with CSS animation on Chrome for Android (and eventually desktop Chrome) as a notable exception.
Using CSS animation simplifies the application and lets animations run smoothly even when other JavaScript is busy. As an example, a button click that runs JavaScript for 180ms would cause jank in a JavaScript-driven animation, but the same animation driven by CSS continues unaffected.
Key takeaways
- Producing a frame for every screen refresh matters. V-synced animation has a large positive effect on how an app feels.
- CSS animation is the most reliable way to get v-synced animation in Chrome and other modern browsers. When you need more flexibility than CSS provides, use
requestAnimationFrame-based animation. - Keep
requestAnimationFramecallbacks short (under 15ms), and make sure other event handlers are not blocking them from running.
V-synced animation applies beyond simple UI effects — it also matters for Canvas2D, WebGL, and even scrolling on static pages. Those are the topics to examine next, with the same frame-budget and timing principles in mind.



