How requestAnimationFrame Works Under the Hood in Chrome: A Deep Dive
This article explains the inner workings of requestAnimationFrame, detailing why setTimeout/setInterval fall short, how Chrome's animation controller registers and fires callbacks, and the call stack that drives frame updates, complete with code excerpts and diagrams.
Most developers now use
requestAnimationFramefor JavaScript animations, but before its introduction the only options were
setTimeoutor
setInterval, which suffer from timing inaccuracies, over‑rendering, and lack of synchronization with the browser’s rendering loop.
The core issue is timing:
setTimeoutand
setIntervalprovide fixed‑interval timers without knowledge of when the browser is ready to render the next frame. The browser, however, can determine the optimal moment for a new frame, and
requestAnimationFramebridges this gap by letting the engine schedule callbacks at the right time.
In Chrome (based on Android 4.4), the implementation is surprisingly simple. When a script calls
requestAnimationFrame(callback), the engine creates a
ScriptedAnimationControllerinstance and registers the callback:
<code>int Document::requestAnimationFrame(PassRefPtr<RequestAnimationFrameCallback> callback)</code>The controller stores callbacks, checks that the page is visible, and ensures they are only executed on the next animation frame. It also guards against background tabs:
<code>if (!page()) return;
view->serviceScriptedAnimations(monotonicFrameBeginTime);</code>When the browser is ready to paint a new frame, it walks a call stack that eventually reaches
PageWidgetDelegate::animateor
WebViewImpl::animate. These functions invoke
serviceScriptedAnimations, which iterates over the pending callbacks, converts the high‑resolution time to the appropriate base (legacy or modern), and calls each callback’s
handleEventmethod.
After callbacks run, the controller removes any that have fired or been cancelled and, if more callbacks remain, schedules another animation frame. The scheduling logic uses
base::Timeand
base::TimeDeltato target 60 FPS when vsync is enabled, otherwise it runs as fast as possible.
Key steps of the mechanism are:
Register the callback with
requestAnimationFrame.
The browser’s render loop triggers
animateduring a frame update.
animatecalls the controller, which executes all registered callbacks.
This ownership transfer of frame timing to the browser kernel synchronizes animation updates with rendering, avoids desynchronization, and gives the browser room to optimize.
Various entry points such as
RenderWidget::didInvalidateRector
RenderWidget::CompleteInitcan request an animation check, ensuring that any change that might affect the visual output schedules a new frame.
The following diagram (from the official spec) illustrates the overall flow of
requestAnimationFrame:
Note: RenderWidget lives in content/renderer/render_widget.cc , not in core/rendering/RenderWidget.cpp . The author was initially confused because the latter contains no animation code.
In summary,
requestAnimationFrameworks by delegating the timing of animation callbacks to the browser’s rendering engine, which registers callbacks, fires them at the optimal moment, and reschedules as needed, providing smoother and more efficient animations than traditional timers.
Taobao Frontend Technology
The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.