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 requestAnimationFrame for JavaScript animations, but before its introduction the only options were setTimeout or setInterval, which suffer from timing inaccuracies, over‑rendering, and lack of synchronization with the browser’s rendering loop.
The core issue is timing: setTimeout and setInterval provide 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 requestAnimationFrame bridges 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 ScriptedAnimationController instance and registers the callback:
int Document::requestAnimationFrame(PassRefPtr<RequestAnimationFrameCallback> callback)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:
if (!page()) return;
view->serviceScriptedAnimations(monotonicFrameBeginTime);When the browser is ready to paint a new frame, it walks a call stack that eventually reaches PageWidgetDelegate::animate or 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 handleEvent method.
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::Time and base::TimeDelta to 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 animate during a frame update. animate calls 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::didInvalidateRect or RenderWidget::CompleteInit can 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, requestAnimationFrame works 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
