Understanding the Three.js Animation Loop
This article explains how the animation loop functions within a
Three.js application. You will learn about the role of the browser’s
native requestAnimationFrame API, how the loop updates 3D
objects, and how to achieve consistent, frame-rate-independent motion
across different devices.
The Core Mechanism: requestAnimationFrame
At the heart of any interactive Three.js application is the animation loop (often called the render loop). To create the illusion of motion, the application must redraw the 3D scene dozens of times per second.
Instead of using traditional JavaScript timing functions like
setInterval or setTimeout, Three.js utilizes
the browser’s native requestAnimationFrame API. This API is
specifically optimized for animations because it: * Synchronizes the
redraw rate with the user’s monitor refresh rate (typically 60Hz to
144Hz). * Pauses execution automatically when the user switches tabs,
saving CPU, GPU, and battery power.
Implementing a Basic Animation Loop
To set up an animation loop, you define a recursive function that
updates object properties and renders the scene, then passes itself as a
callback to requestAnimationFrame.
Here is the standard structural layout of a Three.js animation loop:
// Define the animation loop function
function animate() {
// Queue the next frame
requestAnimationFrame(animate);
// 1. Update 3D object properties (e.g., rotation, position)
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.01;
// 2. Render the updated scene from the perspective of the camera
renderer.render(scene, camera);
}
// Start the loop
animate();In this cycle, the browser runs the animate function,
updates the mesh’s rotation, draws the new pixels to the screen using
renderer.render(), and schedules the next update.
Achieving Frame-Rate Independence
Using fixed values for movement (such as
mesh.rotation.x += 0.01) presents a problem: objects will
move faster on high-refresh-rate screens (like 144Hz monitors) and
slower on lagging devices.
To ensure animations run at the exact same speed regardless of the
device’s performance, you must use elapsed time (delta time) to
calculate movements. Three.js provides a Clock utility for
this purpose:
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
// Get the time elapsed since the clock started
const elapsedTime = clock.getElapsedTime();
// Rotate exactly 1 radian per second, regardless of frame rate
mesh.rotation.y = elapsedTime * 1.0;
renderer.render(scene, camera);
}
animate();By multiplying your movement variables by the elapsed time, the animation remains uniform across all hardware configurations.
The WebXR Alternative: setAnimationLoop
While requestAnimationFrame is ideal for standard web
browsers, it does not work for WebXR (Virtual Reality and Augmented
Reality) projects.
For cross-platform compatibility, Three.js provides a built-in helper method directly on the renderer. This method should be used if you plan to support VR/AR headsets:
// This replaces requestAnimationFrame and works for both web and WebXR
renderer.setAnimationLoop(() => {
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
});