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

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.