Three.js Cheatsheet
Animation
Use this Three.js reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
AnimationMixer
The AnimationMixer is the central controller. One mixer per root object (or skeleton root).
const mixer = new THREE.AnimationMixer(model); // model = THREE.Object3D // Advance in render loop const clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const delta = clock.getDelta(); mixer.update(delta); // REQUIRED every frame renderer.render(scene, camera); } animate();
AnimationClip
// From GLTF const clips = gltf.animations; // Array<AnimationClip> // Find by name const runClip = THREE.AnimationClip.findByName(clips, 'Run'); // Create manually const track = new THREE.VectorKeyframeTrack( '.position', // property path [0, 1, 2], // time keys (seconds) [0,0,0, 0,3,0, 0,0,0] // values (3 per key for Vector3) ); const clip = new THREE.AnimationClip('Bounce', 2, [track]);
AnimationAction
const action = mixer.clipAction(clip); action.play(); action.pause(); action.stop(); action.reset(); // reset to time 0 action.reset().play(); // restart from beginning // Check state action.isRunning(); action.isPaused();
AnimationAction Properties
| Property | Type | Default | Description |
|---|---|---|---|
action.loop | LoopMode | LoopRepeat | LoopOnce, LoopRepeat, LoopPingPong |
action.repetitions | number | Infinity | Times to repeat; use with LoopOnce |
action.clampWhenFinished | boolean | false | Hold last frame when done |
action.weight | number | 1 | Blend weight (0–1) |
action.timeScale | number | 1 | Playback speed; -1 = reverse |
action.time | number | 0 | Current playback time (seconds) |
action.enabled | boolean | true | Enable/disable without stopping |
action.paused | boolean | false | Pause flag |
Blending / Crossfading
// Fade between two actions const idleAction = mixer.clipAction(idleClip); const walkAction = mixer.clipAction(walkClip); idleAction.play(); function switchToWalk() { walkAction.reset(); walkAction.crossFadeFrom(idleAction, 0.5, true); // duration 0.5s, warp=true walkAction.play(); } // Manual weight control idleAction.weight = 1 - t; walkAction.weight = t; // 0 ≤ t ≤ 1 // Must call mixer.clipAction(clip).enabled = true for weight to matter
Keyframe Tracks
| Track Class | Property | Value Format |
|---|---|---|
VectorKeyframeTrack | .position, .scale | [x,y,z, x,y,z, ...] |
QuaternionKeyframeTrack | .quaternion | [x,y,z,w, ...] |
NumberKeyframeTrack | .material.opacity, .rotation.y | [v, v, ...] |
ColorKeyframeTrack | .material.color | [r,g,b, ...] |
BooleanKeyframeTrack | .visible | [bool, ...] |
StringKeyframeTrack | custom | [str, ...] |
// Position track: 0s at origin, 1s at (5,0,0), 2s back at origin const posTrack = new THREE.VectorKeyframeTrack( '.position', [0, 1, 2], [0, 0, 0, 5, 0, 0, 0, 0, 0] ); // Quaternion rotation track const euler = new THREE.Euler(0, Math.PI, 0); const quat = new THREE.Quaternion().setFromEuler(euler); const quatTrack = new THREE.QuaternionKeyframeTrack( '.quaternion', [0, 1], [0, 0, 0, 1, quat.x, quat.y, quat.z, quat.w] ); // Opacity fade track const opacityTrack = new THREE.NumberKeyframeTrack( '.material.opacity', [0, 1], [1, 0] ); const clip = new THREE.AnimationClip('MyAnim', 2, [posTrack, quatTrack, opacityTrack]);
Interpolation Modes
// Per-track interpolation track.setInterpolation(THREE.InterpolateLinear); // default track.setInterpolation(THREE.InterpolateSmooth); // cubic (Catmull-Rom) track.setInterpolation(THREE.InterpolateDiscrete); // step/snap
Mixer Events
mixer.addEventListener('finished', (event) => { console.log('Animation finished:', event.action.getClip().name); }); mixer.addEventListener('loop', (event) => { console.log('Loop iteration:', event.action.getClip().name); });
Morph Target Animation
// Geometry must have morphAttributes const geo = new THREE.SphereGeometry(1, 32, 32); // Create morph target (a squashed sphere) const morphGeo = new THREE.SphereGeometry(1, 32, 32); // (In practice, morph target arrays come from your 3D app / GLTF) geo.morphAttributes.position = [morphGeo.attributes.position]; const mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({ morphTargets: true, // REQUIRED })); // Set influence (0–1) mesh.morphTargetInfluences[0] = 0.5; // GLTF morph targets — same API gltf.scene.traverse((child) => { if (child.isMesh && child.morphTargetInfluences) { child.morphTargetInfluences[0] = 0.7; // 70% of first morph } });
Skinned Mesh / Skeleton
// Usually loaded from GLTF, but can be built manually const bones = []; const root = new THREE.Bone(); root.position.y = 0; bones.push(root); const hip = new THREE.Bone(); hip.position.y = 1; root.add(hip); bones.push(hip); const skeleton = new THREE.Skeleton(bones); const geo = /* your geometry with skinIndex + skinWeight attributes */; const mesh = new THREE.SkinnedMesh(geo, material); mesh.add(root); mesh.bind(skeleton); // After bind, animate by rotating bones root.rotation.z = Math.sin(t) * 0.5; // Skeleton helper const helper = new THREE.SkeletonHelper(mesh); scene.add(helper);
GSAP Integration (Popular Pattern)
import gsap from 'gsap'; // Tween a mesh position gsap.to(mesh.position, { x: 5, y: 2, duration: 1.5, ease: 'power2.inOut' }); // Tween rotation gsap.to(mesh.rotation, { y: Math.PI, duration: 2 }); // Sequence const tl = gsap.timeline(); tl.to(mesh.position, { y: 3, duration: 1 }) .to(mesh.rotation, { y: Math.PI, duration: 0.5 }) .to(mesh.position, { y: 0, duration: 1 }); // Tween material property gsap.to(mat, { opacity: 0, duration: 1, onStart: () => (mat.transparent = true) });
requestAnimationFrame Utilities
// Stop the loop let animId; function animate() { animId = requestAnimationFrame(animate); renderer.render(scene, camera); } animate(); // To stop: cancelAnimationFrame(animId); // Throttle to 30 fps let lastTime = 0; function animate(time) { requestAnimationFrame(animate); if (time - lastTime < 1000 / 30) return; lastTime = time; renderer.render(scene, camera); }
Gotchas
mixer.update(delta)must receive seconds, not milliseconds.clock.getDelta()returns seconds;performance.now()returns ms — divide by 1000.
action.stop()also resets state. Useaction.paused = trueto pause while keeping time position.
When crossfading, both actions must be playing — reset and play the incoming action before calling
crossFadeFrom.
LoopOnceactions stop and hold the last frame only ifclampWhenFinished = true. Otherwise they reset to the first frame.
Morph targets require
morphTargets: trueon the material (standard and physical materials support this). There's also amorphNormals: truefor correct lighting.
Multiple
AnimationMixerinstances for the same model don't share state — each has independent playback. Use a single mixer per skeleton root.
Do not reuse an
AnimationClip's tracks — they are mutated by the mixer internally. Clone the clip if you need independent playback on multiple objects.