Course outline
The Animation System
In one line
A clip stores keyframes, a mixer plays them on a model, and an action is one playing copy you can pause, loop and crossfade. You drive a real animated fox and learn the nine reasons an action never plays.
Think of it like this
A film reel holds the pictures, the projector plays them, and the projectionist can pause, rewind or fade from one reel to the next. In three.js the clip is the reel, the mixer is the projector for one model, and an action is one reel loaded in it.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- keyframe
- A value at one moment in time. The values between two keyframes are worked out by interpolation.
- AnimationClip
- A named, timed bundle of tracks: one whole motion such as Walk.
- AnimationMixer
- The player for one model. It owns the clock and blends actions. Call
update(delta)every frame. - AnimationAction
- One playing (or waiting) copy of a clip, with its own time, speed, weight and loop mode.
- crossfade
- Handing over smoothly from one action to another by moving their weights.
- weight
- How strongly an action counts, from 0 to 1. Weights of running actions are blended.
Step by step
01Build a clip by hand
The first example makes a clip with one track: the node named crank, its z rotation, from 0 at 0 s to -2π at 2 s. It prints the track's name, times and values.
02Play it with a fixed step
mixer.update(1 / 60) 120 times is exactly 2 s. After 30 updates the crank has turned -0.25 turns, after 60 -0.5, after 120 -1. One more update loops: time 0.017.
03Nine attempts
The second example tries nine ways to turn the crank for half a second. Only two turn it: the correct one and reset().play(). See why each of the others does not.
04A real model
The third example loads the Fox, plays Walk, then crossfades to Run. The weights are read after the same fixed steps.
Try it yourself
- 1
A slower crossfade
In the Fox example change
crossFadeTo(run, 0.5, false)tocrossFadeTo(run, 2, false). The weights after 15 and 30 updates become walk 0.88 and run 0.13, then walk 0.75 and run 0.25: a fade over 2 s is only a quarter done after half a second. - 2
Play Survey
Replace
"Walk"with"Survey". The clip is 3.417 s long, so the first log line after 30 updates shows time 0.500 of 3.417.
Code & diagrams
No canvas. Everything is driven with a fixed step of 1/60 s, so the output is the same every time.
Console
Run the scene to see its console.Expected console output
clip: pedal 2 s, 1 track: crank.rotation[z] | times [ 0, 2 ] values [ '0.000', '-6.283' ]
after 30 updates of 1/60 s: time 0.500, crank turned -0.250 turns
after 60 updates of 1/60 s: time 1.000, crank turned -0.500 turns
after 120 updates of 1/60 s: time 2.000, crank turned -1.000 turns
one more update (loops by default): time 0.017 turns -0.008The misspelled node name is the only one that prints a warning. The rest are silent.
Console
Run the scene to see its console.Expected console output
play() and update() turns: -0.250
forgot play() turns: 0.000
forgot mixer.update() turns: 0.000
THREE.PropertyBinding: No target node found for track: cranck.rotation[z].
track names a node that is not there turns: 0.000
setEffectiveWeight(0) turns: 0.000
paused = true turns: 0.000
timeScale = 0 turns: 0.000
LoopOnce, finished, then play() again turns: 0.000
LoopOnce, finished, reset().play() turns: -0.250Fox by PixelMannen (CC0), rigging and animation by tomkranis (CC BY 4.0), glTF conversion by @AsoboStudio and @scurest (CC BY 4.0). Open the scene tree: one Group, a root bone chain and one SkinnedMesh.
Console
Run the scene to see its console.Expected console output
clips: Survey (3.417 s), Walk (0.708 s), Run (1.158 s)
Walk after 30 updates: time 0.500 of 0.708 | weight 1
crossfade starts: walk 1.00, run 1.00
after 15 updates (0.25 s): walk 0.50, run 0.50
after 30 updates (0.5 s): walk 0.00, run 1.00Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
A misspelled node
In the first example change "crank.rotation[z]" to "cranck.rotation[z]".
Break #2
A misspelled clip name
In the Fox example change "Run" to "run" (names are case-sensitive).
Break #3
No update
In the second example, add { skipUpdate: true } as a third argument to the first row, attempt("play() and update()", (a) => a.play()).
Myth vs fact
Myth
Playing an action animates the model.
Fact
Only mixer.update(delta) does. play() marks the action as running.
Myth
play() on a finished action plays it again.
Fact
A finished LoopOnce action is switched off. Call reset().play(), as the last row of the table shows.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Each model needs its own mixer. A mixer's cost grows with the number of tracks it drives, so a crowd of 200 foxes updates 200 × 21 tracks every frame on the CPU.
- ▸
clampWhenFinished = truekeeps the last pose withpaused = trueinstead ofenabled = false: use it for a door that stays open. The mixer firesfinishedandloopevents on itself, with the action in the event. - ▸
A weight of 0 does not stop the action's clock, it just stops it affecting the pose.
pausedstops the clock.crossFadeTo(..., true)(warp) also stretches the clips to match their durations so footsteps stay in step.
Remember this
- 1
The problem this solves: writing every motion by hand in the loop (Topic 9.1) works for a wheel. It does not work for a fox running: 24 bones, 21 of them with their own curve, made by an artist in Blender. You need a way to store, play and blend that data.
- 2
**A
KeyframeTrack** says: nodecrank, propertyrotation[z], takes these values at these times. The name format isnodeName.property. Classes by type:NumberKeyframeTrack,VectorKeyframeTrack(positions, scales),QuaternionKeyframeTrack(rotations),ColorKeyframeTrack,BooleanKeyframeTrack. Between keyframes the values are interpolated (linear by default, smooth or stepped if you ask; quaternions use slerp from Topic 2.6). - 3
**An
AnimationClip** is a name, a duration and a list of tracks. The Fox model has three: Survey (3.417 s), Walk (0.708 s) and Run (1.158 s), each with 21 tracks. A loader gives them to you asgltf.animations. Find one withTHREE.AnimationClip.findByName(gltf.animations, "Run"). - 4
**An
AnimationMixer** is made for one root object:new THREE.AnimationMixer(fox). It finds each track's node by name under that root, and holds the clocks. You must callmixer.update(delta)once per frame: no update, no motion. **mixer.clipAction(clip)** returns anAnimationAction. - 5
An action is a playing copy of a clip.
play()starts it,stop()ends it,reset()rewinds it. Settings:paused,timeScale(2 is double speed, negative plays backwards),weight(0 to 1: how much it counts when several actions run),setLoop(THREE.LoopOnce | LoopRepeat | LoopPingPong, repetitions)andclampWhenFinished(hold the last pose). The default is LoopRepeat, forever. - 6
Crossfade.
walk.crossFadeTo(run, 0.5, false)lowers Walk's weight and raises Run's over 0.5 seconds. The action you fade to must already be playing (run.reset().play()first). The weights change insidemixer.update, which is why the example prints 1.00 and 1.00 right after the call, then 0.50 and 0.50 after a quarter second. - 7
Credits. The Fox is by PixelMannen (CC0). Its rigging and animation are by tomkranis and its glTF conversion by @AsoboStudio and @scurest, both CC BY 4.0, via the Khronos glTF Sample Assets. CC BY means you must credit them wherever you ship it.
Explain it without notes
Name the four objects between an animation file and a moving model, and say what each does.
A walk cycle plays on the Fox but the Run action does nothing after crossFadeTo. Give two likely causes.
Practice
Play the Fox's Survey clip once and hold the last pose.
Write a function switchTo(next) that crossfades the current action to next over 0.3 s, and call it for Walk and Run.
Trade-offs
- ↔
Clip-based animation is best for authored motion. A one-line formula (the wheel) is simpler and cheaper than a clip.
Done when you can
Name the four animation objects and what each does
Build a clip with one track and play it with a fixed step
Crossfade two actions on a real model
Find the reason an action does not play