Listen up. If you're building modern applications, understanding Animations in Three.js 3D WebGL is non-negotiable. This is where simple logic turns into intelligent behavior.
1Building the requestAnimationFrame Render Loop
Static scenes are boring! To make things move, we need a render loop. requestAnimationFrame is your best friend.
The animate() function calls itself every frame via requestAnimationFrame, which asks the browser to run your callback right before the next repaint ā typically 60 times per second on standard displays, and it automatically throttles when the tab is inactive to save battery and CPU.
Unlike setInterval or setTimeout, requestAnimationFrame stays synchronized with the browser's actual repaint cycle, so you avoid tearing and wasted work when the page isn't visible. Each call to renderer.render(scene, camera) redraws the entire scene from the current camera's point of view, so the loop must keep running for anything to appear to move.
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();3D Scene rendered. Objects: 4, Draw Calls: Optimized.
2Updating Object Properties Inside the Loop
Inside the render loop, you update properties like rotation before calling render.
Because animate() runs on every frame, incrementing cube.rotation.x and cube.rotation.y by a small fixed amount each pass produces a smooth, continuous spin ā Three.js re-evaluates the object's world matrix from these Euler angles before the next render call.
Order matters here: you mutate the object's properties first, then call renderer.render, so the frame you see always reflects the latest state. Using a fixed increment like 0.01 ties rotation speed to frame rate, which is fine for a first demo but will spin faster on high-refresh-rate monitors ā a limitation solved later with delta time.
function animate() {
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}3D Scene rendered. Objects: 4, Draw Calls: Optimized.
3Animating in React Three Fiber with useFrame
If you are using React Three Fiber, you don't write requestAnimationFrame! You use the useFrame hook.
React Three Fiber already owns the render loop internally, so calling requestAnimationFrame yourself would fight the reconciler and likely double-render or desync from React's lifecycle. The useFrame hook lets you tap into that existing loop safely on every frame.
The callback receives state (camera, scene, clock, and more) and delta, the time in seconds since the last frame. Multiplying by delta instead of using a fixed increment keeps the animation speed consistent regardless of the viewer's frame rate. Mutating meshRef.current directly, rather than storing it in React state, avoids triggering a re-render for a value that changes 60 times a second.
import { useFrame } from '@react-three/fiber';
useFrame((state, delta) => {
meshRef.current.rotation.x += delta;
});3D Scene rendered. Objects: 4, Draw Calls: Optimized.
4Step-by-Step Breakdown
Static scenes are boring! To make things move, we need a render loop. requestAnimationFrame is your best friend.
Inside the render loop, you update properties like rotation before calling render.
Which browser API is used to synchronize your animation with the screen's refresh rate?
- āsetInterval
- āsetTimeout
- ārequestAnimationFrame
If you are using React Three Fiber, you don't write requestAnimationFrame! You use the useFrame hook.
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Reduced Motion Support
Continuous rotation and camera movement can trigger discomfort for users with vestibular disorders, so respect the prefers-reduced-motion media query and pause or slow animations when it's set.
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
// skip or simplify animation updates
}SEO Implications
- 1
Crawlability of Canvas Content
A WebGL canvas is opaque to search engine crawlers, so none of the animated 3D content itself is indexable ā keep essential text, product details, and navigation in real HTML alongside the canvas instead of relying on the animation to convey information.
Best Practices
Use Delta Time, Not Frame Count
Multiply movement and rotation by the delta time between frames (via THREE.Clock or useFrame's delta argument) so animation speed stays consistent across 30Hz, 60Hz, and 144Hz displays.
Cancel the Loop on Unmount
Store the ID returned by requestAnimationFrame and call cancelAnimationFrame when the component unmounts or the scene is disposed, otherwise the loop keeps running against a destroyed renderer and leaks memory.
Frequent Bugs
Animation speed varies wildly between devices.
This happens when rotation or position increments are hardcoded per frame instead of scaled by delta time; multiply by clock.getDelta() (or the delta argument in useFrame) so motion stays consistent regardless of frame rate.
Real-World Examples
Product Configurator Idle Spin
E-commerce 3D product viewers use a render loop like this to slowly auto-rotate a model when the user isn't interacting, pausing the loop the instant the user grabs OrbitControls so the two animations never fight over the same rotation property.
let idleSpin = true;
function animate() {
requestAnimationFrame(animate);
if (idleSpin) model.rotation.y += 0.002;
renderer.render(scene, camera);
}