Three.js Clock for Frame-Rate Independent Movement
In 3D web graphics, animations must run at a consistent speed
regardless of the user’s device performance or screen refresh rate. This
article provides a straightforward guide on using the Three.js
Clock utility to calculate delta time, ensuring your 3D
object movements remain uniform across 60Hz, 144Hz, and varying
frame-rate environments.
The Problem with Frame-Rate Dependent Movement
When animating objects in a requestAnimationFrame loop,
updating positions by a fixed value per frame causes issues. For
example, code like mesh.position.x += 0.1 will move the
object more than twice as fast on a 144Hz monitor as it would on a 60Hz
monitor. If the frame rate drops due to performance lag, the animation
will slow down.
To solve this, movement must be calculated based on elapsed time rather than elapsed frames.
Implementing Three.js Clock
The THREE.Clock utility tracks the time elapsed since
the clock started, as well as the time difference between the current
frame and the previous frame (known as “delta time”).
1. Initialize the Clock
Instantiate the clock outside of your animation loop, typically alongside your scene and renderer setup:
import * as THREE from 'three';
// Create the clock
const clock = new THREE.Clock();2. Retrieve Delta Time in the Animation Loop
Inside your render loop, call the .getDelta() method.
This returns the time in seconds that has passed since
.getDelta() was last called.
Multiply your desired speed (units per second) by this delta value:
const speed = 5; // Move 5 units per second
function animate() {
requestAnimationFrame(animate);
// Get the time elapsed since the last frame
const deltaTime = clock.getDelta();
// Multiply speed by deltaTime for consistent movement
mesh.position.x += speed * deltaTime;
renderer.render(scene, camera);
}
animate();If the frame rate drops to 30 frames per second,
deltaTime will be roughly 0.033 seconds. If
the frame rate is a smooth 60 frames per second, deltaTime
will be roughly 0.016 seconds. In both cases, the mesh will
move exactly 5 units over the span of one real-world second.
Best Practices
- Call
.getDelta()Once Per Frame: Avoid callingclock.getDelta()multiple times within a single animation frame. Each call resets the internal clock tracker, which will return near-zero values on subsequent calls in the same frame. Instead, store the result in a singledeltaTimevariable and use it for all animations. - Physics Engines: If you are using a physics engine (like Cannon.js or Rapier), use the delta time to step the physics world forward rather than manually shifting positions.