Three.js setAnimationLoop vs requestAnimationFrame
Transitioning from standard web 3D to WebVR/WebXR in Three.js
requires a shift in how you handle your rendering loop. While
traditional desktop and mobile 3D projects rely on the browser’s native
window.requestAnimationFrame, virtual and augmented reality
experiences require Three.js’s built-in
renderer.setAnimationLoop. This article explains the
technical differences between these two animation methods, why WebXR
demands a specialized loop, and how to implement a unified rendering
loop that seamlessly supports both standard and XR environments.
The Standard Loop: requestAnimationFrame
In a standard web application, the animation loop is driven by the
browser’s native window API. The requestAnimationFrame
(rAF) method tells the browser that you wish to perform an animation and
requests that the browser calls a specified function to update an
animation before the next repaint.
function animate() {
requestAnimationFrame(animate);
// Update physics and animations
mesh.rotation.x += 0.01;
renderer.render(scene, camera);
}
// Start the loop
requestAnimationFrame(animate);This approach is highly optimized for standard web browsers. It pauses when the user switches tabs, saving CPU and GPU resources. However, it is bound entirely to the window’s display lifecycle and refresh rate.
The WebXR Loop: setAnimationLoop
When you enter a WebXR session (VR or AR), the rendering target shifts from the browser window to the XR headset or mobile device screen. XR devices run on their own hardware clocks and refresh rates (such as 90Hz, 120Hz, or higher) which are completely independent of the browser window’s refresh rate.
To handle this, WebXR uses its own internal render loop via
XRSession.requestAnimationFrame. Because the standard
window.requestAnimationFrame cannot sync with XR hardware,
Three.js provides a wrapper method:
WebGLRenderer.setAnimationLoop.
function animate(time, frame) {
// Update physics and animations
mesh.rotation.x += 0.01;
renderer.render(scene, camera);
}
// Start the loop
renderer.setAnimationLoop(animate);To stop the animation loop when using this method, you pass
null to the renderer:
// Stop the loop
renderer.setAnimationLoop(null);Key Differences and Why They Matter
- Hardware Synchronization:
requestAnimationFramesyncs with the monitor’s refresh rate (typically 60Hz).setAnimationLoopdynamically adapts to the XR device’s target frame rate, which is critical for preventing motion sickness in VR. - Argument Passing: The callback function in
setAnimationLoopreceives two arguments:time(a high-resolution timestamp) andframe(the currentXRFrameobject). The standardrequestAnimationFramecallback only receives a timestamp. - Scope of execution: If you use standard
requestAnimationFrameinside an active XR session, the rendering will fail to update inside the headset entirely, resulting in a frozen display.
Best Practice: Use setAnimationLoop Globally
The modern recommendation for Three.js development is to use
renderer.setAnimationLoop for all projects, even those that
do not currently use WebXR.
Under the hood, Three.js is designed to detect if an active WebXR
session is running. If no XR session is active,
setAnimationLoop automatically falls back to the standard
window.requestAnimationFrame. If an XR session begins,
Three.js seamlessly transitions the callback to the XR device’s internal
rendering loop without requiring any code changes on your part. This
makes your codebase future-proof and ready for spatial computing out of
the box.