threejs-impl-animation
threejs-impl-animation
Quick Reference
Architecture
AnimationClip (data: array of KeyframeTrack objects)
└── AnimationAction (playback controller: play, pause, fade, crossfade)
└── AnimationMixer (master scheduler: one per animated root object)
└── Clock (provides delta time for mixer.update)
ALWAYS create exactly ONE AnimationMixer per animated root object.
ALWAYS call mixer.update(delta) every frame inside the render loop.
NEVER instantiate AnimationAction directly -- ALWAYS use mixer.clipAction(clip).
Essential Imports
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
Minimal Animation Setup
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const clock = new THREE.Clock();
let mixer;
const loader = new GLTFLoader();
loader.load('character.glb', (gltf) => {
scene.add(gltf.scene);
mixer = new THREE.AnimationMixer(gltf.scene);
// Play all animations from the GLTF file
gltf.animations.forEach((clip) => {
mixer.clipAction(clip).play();
});
});
function animate() {
const delta = clock.getDelta();
if (mixer) mixer.update(delta);
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
Critical Warnings
NEVER forget to call mixer.update(delta) in the render loop -- animations will NOT play without it.
NEVER use new Date() or performance.now() to compute delta manually -- ALWAYS use THREE.Clock or renderer.setAnimationLoop which provides stable frame timing.
NEVER call mixer.clipAction(clip) repeatedly in the render loop -- it caches internally, but the lookup is unnecessary overhead. ALWAYS store the returned action in a variable.
NEVER call .play() every frame -- call it ONCE to start playback. Calling .play() again on an already-playing action has no effect, but it signals misunderstanding.
ALWAYS call .reset() before .play() when restarting a stopped or finished action, or the action may resume from its last position.
ALWAYS set action.clampWhenFinished = true when using LoopOnce -- otherwise the action resets to the first frame when finished.
AnimationMixer
The master scheduler that drives all animation actions for a single object hierarchy.
Constructor
const mixer = new THREE.AnimationMixer(rootObject);
rootObject is the root Object3D of the animated model (typically gltf.scene).
Properties
| Property | Type | Default | Description |
|---|---|---|---|
.time |
number | 0 |
Global mixer time in seconds |
.timeScale |
number | 1 |
Global speed multiplier; 0 pauses ALL actions |
Methods
| Method | Returns | Description |
|---|---|---|
.clipAction(clip, root?, blendMode?) |
AnimationAction |
Returns or creates an action for the clip |
.existingAction(clip, root?) |
AnimationAction | null |
Returns previously created action or null |
.update(delta) |
this |
Advances mixer by delta seconds -- MUST call every frame |
.setTime(seconds) |
this |
Sets global time, updates all actions |
.stopAllAction() |
this |
Deactivates all scheduled actions |
.getRoot() |
Object3D |
Returns the mixer's root object |
.uncacheAction(clip, root?) |
void |
Deallocates cached action |
.uncacheClip(clip) |
void |
Deallocates clip data |
.uncacheRoot(root) |
void |
Deallocates root object data |
Events
Listen via mixer.addEventListener(type, callback):
| Event | Fires When |
|---|---|
'finished' |
Action completes (ONLY with LoopOnce + clampWhenFinished = true) |
'loop' |
Action completes a loop iteration |
Event object properties: { action, loopDelta, type }.
Blend Modes
Pass as the third argument to mixer.clipAction(clip, root, blendMode):
| Constant | Behavior |
|---|---|
THREE.NormalAnimationBlendMode |
Standard blending (default) |
THREE.AdditiveAnimationBlendMode |
Layered on top of base animation |
AnimationAction
Controls playback of a single animation clip. NEVER instantiate directly.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
.blendMode |
number | NormalAnimationBlendMode |
Blending strategy |
.clampWhenFinished |
boolean | false |
Pause at last frame when done |
.enabled |
boolean | true |
Disable without resetting |
.loop |
number | LoopRepeat |
Loop mode |
.paused |
boolean | false |
Freeze playback |
.repetitions |
number | Infinity |
Loop count |
.time |
number | 0 |
Local time in seconds |
.timeScale |
number | 1 |
Speed: 0 pauses, negative reverses |
.weight |
number | 1 |
Blend influence [0, 1] |
.zeroSlopeAtEnd |
boolean | true |
Smooth interpolation at loop end |
.zeroSlopeAtStart |
boolean | true |
Smooth interpolation at loop start |
Loop Modes
| Constant | Behavior |
|---|---|
THREE.LoopOnce |
Plays once, stops |
THREE.LoopRepeat |
Restarts from beginning each loop |
THREE.LoopPingPong |
Alternates forward/backward |
Playback Methods
| Method | Description |
|---|---|
.play() |
Start playback |
.stop() |
Stop and reset to start |
.reset() |
Reset time, weight, speed to initial state |
.startAt(mixerTime) |
Delay start until specified mixer time |
Fading and Crossfade Methods
| Method | Description |
|---|---|
.fadeIn(duration) |
Fade weight from 0 to 1 |
.fadeOut(duration) |
Fade weight from 1 to 0 |
.crossFadeFrom(fadeOutAction, duration, warp) |
Crossfade from another action into this one |
.crossFadeTo(fadeInAction, duration, warp) |
Crossfade from this action to another |
.stopFading() |
Cancel any active fade |
Speed and Timing Methods
| Method | Description |
|---|---|
.halt(duration) |
Decelerate timeScale to 0 over duration |
.warp(startScale, endScale, duration) |
Smoothly transition playback speed |
.stopWarping() |
Cancel any active warp |
.setDuration(seconds) |
Adjust timeScale so one loop takes exactly seconds |
.setEffectiveTimeScale(scale) |
Set effective time scale |
.setEffectiveWeight(weight) |
Set effective weight |
.setLoop(mode, repetitions) |
Set loop mode and count |
.syncWith(otherAction) |
Synchronize time with another action |
Query Methods
| Method | Returns | Description |
|---|---|---|
.isRunning() |
boolean | true only if actively playing |
.isScheduled() |
boolean | true if .play() was called |
.getClip() |
AnimationClip |
The associated clip |
.getMixer() |
AnimationMixer |
The owning mixer |
.getRoot() |
Object3D |
The root object |
.getEffectiveTimeScale() |
number | Computed time scale |
.getEffectiveWeight() |
number | Computed weight |
AnimationClip
A reusable set of keyframe tracks. Typically loaded from GLTF files.
Constructor
const clip = new THREE.AnimationClip(name, duration, tracks, blendMode);
name-- string identifier (GLTF clips use names from the file)duration-- seconds;-1to auto-calculate from trackstracks-- array ofKeyframeTrackobjectsblendMode-- optional blend mode constant
Key Static Methods
| Method | Description |
|---|---|
AnimationClip.findByName(arrayOrObject, name) |
Look up clip by name |
AnimationClip.CreateFromMorphTargetSequence(name, targets, fps, noLoop) |
Create clip from morph targets |
AnimationClip.parse(json) |
Deserialize from JSON |
KeyframeTrack Types
| Track Type | Value Type | Use Case |
|---|---|---|
VectorKeyframeTrack |
Vector3 | Position, scale |
QuaternionKeyframeTrack |
Quaternion | Rotation (uses slerp) |
NumberKeyframeTrack |
number | Opacity, intensity |
BooleanKeyframeTrack |
boolean | Visibility toggles |
ColorKeyframeTrack |
Color | Color animation |
StringKeyframeTrack |
string | Discrete string values |
Interpolation Modes
| Constant | Behavior |
|---|---|
THREE.InterpolateDiscrete |
Step function, no smoothing |
THREE.InterpolateLinear |
Linear interpolation (default) |
THREE.InterpolateSmooth |
Cubic spline interpolation |
PropertyBinding Path Format
"meshName.position" // animate position
"meshName.material.opacity" // animate material property
"meshName.morphTargetInfluences[0]" // animate morph target
"boneName.quaternion" // animate bone rotation
Clock
Constructor
const clock = new THREE.Clock(autoStart); // autoStart defaults to true
Methods
| Method | Returns | Description |
|---|---|---|
.getDelta() |
number | Seconds since last getDelta() call |
.getElapsedTime() |
number | Total elapsed time in seconds |
.start() |
void | Start the clock |
.stop() |
void | Stop without resetting |
Crossfade Pattern (Character State Machine)
const actions = {};
gltf.animations.forEach((clip) => {
actions[clip.name] = mixer.clipAction(clip);
});
let currentAction = actions['Idle'];
currentAction.play();
function switchAction(toName, duration = 0.5) {
const toAction = actions[toName];
toAction.reset();
toAction.setEffectiveTimeScale(1);
toAction.setEffectiveWeight(1);
toAction.crossFadeFrom(currentAction, duration, true);
toAction.play();
currentAction = toAction;
}
ALWAYS call .reset() on the incoming action before crossfading.
ALWAYS store the current action reference for the next transition.
Additive Animation Blending
const baseAction = mixer.clipAction(baseClip);
const additiveAction = mixer.clipAction(
additiveClip, undefined, THREE.AdditiveAnimationBlendMode
);
baseAction.play();
additiveAction.play();
additiveAction.setEffectiveWeight(0.5);
Use additive blending for layered effects: breathing, damage reactions, aim offsets.
Morph Target Animation
// Manual control
mesh.morphTargetInfluences[0] = Math.sin(elapsed) * 0.5 + 0.5;
// Via GLTF animation clip (preferred)
const morphAction = mixer.clipAction(morphClip);
morphAction.play();
Reference Links
- references/methods.md -- Full API signatures
- references/examples.md -- Working code examples
- references/anti-patterns.md -- What NOT to do
Official Sources
- https://threejs.org/docs/#api/en/animation/AnimationMixer
- https://threejs.org/docs/#api/en/animation/AnimationAction
- https://threejs.org/docs/#api/en/animation/AnimationClip
- https://threejs.org/docs/#api/en/animation/KeyframeTrack
- https://threejs.org/docs/#api/en/core/Clock
- https://threejs.org/examples/#webgl_animation_skinning_blending