Animation Loop
The render loop drives every Three.js scene. Use requestAnimationFrame (or the renderer’s setAnimationLoop) + a clock for time-step-aware animation. Stop the loop on unmount.
A real animation loop
EXAMPLE
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, innerWidth/innerHeight, 0.1, 100);
camera.position.z = 5;
const cube = new THREE.Mesh(
new THREE.BoxGeometry(),
new THREE.MeshStandardMaterial({ color: 0x04AA6D }),
);
scene.add(cube);
scene.add(new THREE.AmbientLight(0xffffff, 0.4));
const sun = new THREE.DirectionalLight(0xffffff, 1.5);
sun.position.set(3, 5, 4);
scene.add(sun);
const controls = new OrbitControls(camera, renderer.domElement);
const clock = new THREE.Clock();
// 1) renderer.setAnimationLoop — handles XR + tab visibility correctly
renderer.setAnimationLoop(() => {
const dt = clock.getDelta(); // seconds since last frame
cube.rotation.x += dt;
cube.rotation.y += dt * 0.6;
controls.update();
renderer.render(scene, camera);
});
// 2) Stop it (component unmount, route change, etc.)
renderer.setAnimationLoop(null);
renderer.dispose();
controls.dispose();
// 3) Resize handler — viewport AND camera projection
addEventListener('resize', () => {
renderer.setSize(innerWidth, innerHeight);
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
});
// 4) Pause when tab is hidden — save battery
document.addEventListener('visibilitychange', () => {
if (document.hidden) renderer.setAnimationLoop(null);
else renderer.setAnimationLoop(animate);
});
// 5) Frame-rate independent motion — ALWAYS multiply by dt
cube.position.x += SPEED * dt; // good — same speed at 60fps and 144fps
// cube.position.x += 0.05; // bad — frame-rate dependent
Why it matters
renderer.setAnimationLoop() over a raw requestAnimationFrame. It works in WebXR contexts, integrates with offscreen canvas, and the renderer cleans up when you pass null.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
function loop() {
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
requestAnimationFrame(loop);
}
loop();
Try it Yourself »
Exercise
Browser-friendly render loop.
(animate);
r…AF.
Discussion
Loading…