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