
Threejs Impl Animation
- 21 installs
- 11 repo stars
- Updated July 8, 2026
- openaec-foundation/three.js-claude-skill-package
Helps with ai & agent building tasks.
About
threejs-impl-animation is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- threejs-impl-animation
- AI & Agent Building
- AI-coding skill
Threejs Impl Animation by the numbers
- 21 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #10,307 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/three.js-claude-skill-package --skill threejs-impl-animationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 21 |
|---|---|
| repo stars | ★ 11 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/three.js-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
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` |
.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 trueMethods
| 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
threejs-impl-animation — Anti-Patterns
What NOT to do with the Three.js Animation System. Every entry includes the mistake, why it fails, and the correct approach.
---
Anti-Pattern 1: Forgetting mixer.update(delta) in the Render Loop
WRONG:
const mixer = new THREE.AnimationMixer(model);
mixer.clipAction(clip).play();
function animate() {
// Missing mixer.update(delta) -- animations will NEVER play
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);WHY: The mixer does NOT advance time automatically. Without mixer.update(delta), all actions remain frozen at time 0.
CORRECT:
const clock = new THREE.Clock();
const mixer = new THREE.AnimationMixer(model);
mixer.clipAction(clip).play();
function animate() {
const delta = clock.getDelta();
mixer.update(delta); // ALWAYS call every frame
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);---
Anti-Pattern 2: Using a Fixed Delta Instead of Clock
WRONG:
function animate() {
mixer.update(1 / 60); // Assumes constant 60fps -- breaks on slow devices
renderer.render(scene, camera);
}WHY: Frame rates vary across devices and over time. A fixed delta causes animations to speed up or slow down unpredictably. On a 30fps device, animations play at half speed.
CORRECT:
const clock = new THREE.Clock();
function animate() {
const delta = clock.getDelta(); // Actual time between frames
mixer.update(delta);
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);---
Anti-Pattern 3: Crossfading Without reset() on the Incoming Action
WRONG:
function switchToWalk() {
walkAction.crossFadeFrom(idleAction, 0.5, true);
walkAction.play();
// walkAction may still have stale time/weight from a previous play
}WHY: If the walk action was previously played and stopped, its internal state (time, weight, timeScale) retains old values. The crossfade starts from the wrong position or weight, producing jerky transitions.
CORRECT:
function switchToWalk() {
walkAction.reset(); // ALWAYS reset before crossfading
walkAction.setEffectiveTimeScale(1);
walkAction.setEffectiveWeight(1);
walkAction.crossFadeFrom(idleAction, 0.5, true);
walkAction.play();
}---
Anti-Pattern 4: Using LoopOnce Without clampWhenFinished
WRONG:
const action = mixer.clipAction(clip);
action.setLoop(THREE.LoopOnce, 1);
action.play();
// Animation snaps back to frame 0 after finishingWHY: Without clampWhenFinished = true, the action resets to its initial state when it completes. The model visibly jumps back to the start pose, which looks broken for one-shot animations like door opens, attacks, or death animations.
CORRECT:
const action = mixer.clipAction(clip);
action.setLoop(THREE.LoopOnce, 1);
action.clampWhenFinished = true; // ALWAYS set for LoopOnce
action.play();---
Anti-Pattern 5: Creating a New Mixer Every Frame
WRONG:
function animate() {
const mixer = new THREE.AnimationMixer(model); // New mixer every frame!
mixer.clipAction(clip).play();
mixer.update(clock.getDelta());
renderer.render(scene, camera);
}WHY: Creating a new mixer every frame discards all previous action state. Animations restart from frame 0 every frame, producing a frozen first-frame appearance. It also generates massive garbage collection pressure.
CORRECT:
const mixer = new THREE.AnimationMixer(model); // Create ONCE
const action = mixer.clipAction(clip);
action.play();
function animate() {
mixer.update(clock.getDelta());
renderer.render(scene, camera);
}---
Anti-Pattern 6: Calling play() Every Frame
WRONG:
const action = mixer.clipAction(clip);
function animate() {
action.play(); // Called every frame -- unnecessary
mixer.update(clock.getDelta());
renderer.render(scene, camera);
}WHY: .play() is an activation method, not an update method. While calling it on an already-playing action is harmless (it returns immediately), doing so every frame signals a fundamental misunderstanding of the API. It can also interfere with fading and crossfade transitions by re-activating an action that is being faded out.
CORRECT:
const action = mixer.clipAction(clip);
action.play(); // Call ONCE
function animate() {
mixer.update(clock.getDelta());
renderer.render(scene, camera);
}---
Anti-Pattern 7: Instantiating AnimationAction Directly
WRONG:
const action = new THREE.AnimationAction(mixer, clip); // NEVER do thisWHY: AnimationAction is designed to be created and cached by the mixer. Direct instantiation bypasses the mixer's internal action cache, leading to duplicate actions, broken crossfades, and memory leaks.
CORRECT:
const action = mixer.clipAction(clip); // ALWAYS use mixer.clipAction()---
Anti-Pattern 8: Forgetting to Dispose the Mixer on Model Removal
WRONG:
scene.remove(model);
// Mixer still holds references, continues to call update, leaks memoryWHY: The mixer retains internal references to the model and all its actions. If the model is removed from the scene but the mixer is not cleaned up, it continues to consume memory and CPU during mixer.update().
CORRECT:
mixer.stopAllAction();
mixer.uncacheRoot(model);
scene.remove(model);
mixer = null; // Allow garbage collection---
Anti-Pattern 9: Using Multiple Mixers for the Same Model
WRONG:
const mixer1 = new THREE.AnimationMixer(model);
const mixer2 = new THREE.AnimationMixer(model);
mixer1.clipAction(walkClip).play();
mixer2.clipAction(idleClip).play();WHY: Multiple mixers on the same root object cause conflicting property writes. Both mixers attempt to set the same bone transforms every frame, producing jittering, twitching, or completely broken animation. Crossfade and blending between clips on different mixers is impossible.
CORRECT:
const mixer = new THREE.AnimationMixer(model); // ONE mixer per model
mixer.clipAction(walkClip).play();
mixer.clipAction(idleClip).play();
// Use weight and crossfade to blend between them---
Anti-Pattern 10: Animating material.opacity Without Setting transparent=true
WRONG:
const track = new THREE.NumberKeyframeTrack(
'Mesh.material.opacity',
[0, 1],
[1, 0]
);
// Opacity changes but object remains fully visibleWHY: Three.js materials require material.transparent = true for opacity values below 1 to have any visual effect. Without this flag, the material ignores the opacity property during rendering.
CORRECT:
material.transparent = true; // MUST set before animating opacity
const track = new THREE.NumberKeyframeTrack(
'Mesh.material.opacity',
[0, 1],
[1, 0]
);---
Anti-Pattern 11: Using getDelta() Multiple Times per Frame
WRONG:
function animate() {
mixer1.update(clock.getDelta()); // Returns actual delta
mixer2.update(clock.getDelta()); // Returns ~0 (called immediately after)
renderer.render(scene, camera);
}WHY: clock.getDelta() returns the time since the LAST call to getDelta(). The second call within the same frame returns approximately zero, effectively freezing the second mixer.
CORRECT:
function animate() {
const delta = clock.getDelta(); // Call ONCE per frame
mixer1.update(delta);
mixer2.update(delta); // Same delta for both
renderer.render(scene, camera);
}threejs-impl-animation — Examples
Working code examples for the Three.js Animation System (r160+).
---
Example 1: Load and Play All GLTF Animations
The most common animation workflow: load a GLTF model and play all embedded clips.
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 1.5, 3);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
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 every animation clip 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);---
Example 2: Play a Single Named Clip
Select and play a specific animation by name from the GLTF file.
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);
// Find a specific clip by name
const idleClip = THREE.AnimationClip.findByName(gltf.animations, 'Idle');
if (idleClip) {
const action = mixer.clipAction(idleClip);
action.play();
}
});
function animate() {
const delta = clock.getDelta();
if (mixer) mixer.update(delta);
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);---
Example 3: Crossfade Character State Machine
Smooth transitions between idle, walk, and run states using crossfade.
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const clock = new THREE.Clock();
let mixer;
const actions = {};
let currentAction;
const loader = new GLTFLoader();
loader.load('character.glb', (gltf) => {
scene.add(gltf.scene);
mixer = new THREE.AnimationMixer(gltf.scene);
// Cache all actions by clip name
gltf.animations.forEach((clip) => {
actions[clip.name] = mixer.clipAction(clip);
});
// Start with idle
currentAction = actions['Idle'];
currentAction.play();
});
function switchAction(toName, duration = 0.5) {
if (!actions[toName] || actions[toName] === currentAction) return;
const toAction = actions[toName];
// ALWAYS reset the incoming action before crossfading
toAction.reset();
toAction.setEffectiveTimeScale(1);
toAction.setEffectiveWeight(1);
toAction.crossFadeFrom(currentAction, duration, true);
toAction.play();
currentAction = toAction;
}
// Usage: respond to input
document.addEventListener('keydown', (event) => {
switch (event.key) {
case 'w': switchAction('Walk'); break;
case 'r': switchAction('Run'); break;
case 'i': switchAction('Idle'); break;
}
});
function animate() {
const delta = clock.getDelta();
if (mixer) mixer.update(delta);
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);---
Example 4: Additive Animation Blending
Layer an additive animation (e.g., breathing or damage reaction) on top of a base animation.
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);
// Base animation: normal blending
const idleClip = THREE.AnimationClip.findByName(gltf.animations, 'Idle');
const baseAction = mixer.clipAction(idleClip);
baseAction.play();
// Additive animation: layered on top
const breatheClip = THREE.AnimationClip.findByName(gltf.animations, 'Breathe');
const additiveAction = mixer.clipAction(
breatheClip,
undefined,
THREE.AdditiveAnimationBlendMode
);
additiveAction.play();
additiveAction.setEffectiveWeight(0.5); // control blend strength
});
function animate() {
const delta = clock.getDelta();
if (mixer) mixer.update(delta);
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);---
Example 5: Play-Once Animation with Finished Event
Play an animation exactly once, clamp at the last frame, and detect completion.
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('door.glb', (gltf) => {
scene.add(gltf.scene);
mixer = new THREE.AnimationMixer(gltf.scene);
const openClip = THREE.AnimationClip.findByName(gltf.animations, 'DoorOpen');
const action = mixer.clipAction(openClip);
// Configure play-once behavior
action.setLoop(THREE.LoopOnce, 1);
action.clampWhenFinished = true; // ALWAYS set for LoopOnce
// Listen for completion
mixer.addEventListener('finished', (event) => {
console.log('Animation finished:', event.action.getClip().name);
});
action.play();
});
function animate() {
const delta = clock.getDelta();
if (mixer) mixer.update(delta);
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);---
Example 6: Programmatic KeyframeTrack Animation
Create an animation entirely in code without loading a GLTF file.
import * as THREE from 'three';
const clock = new THREE.Clock();
// Create a simple cube
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
cube.name = 'MyCube';
scene.add(cube);
// Define keyframe tracks
const positionTrack = new THREE.VectorKeyframeTrack(
'MyCube.position', // PropertyBinding path
[0, 1, 2], // times in seconds
[0, 0, 0, 0, 2, 0, 0, 0, 0] // values: [x,y,z] at each time
);
const rotationTrack = new THREE.QuaternionKeyframeTrack(
'MyCube.quaternion',
[0, 1, 2],
[
0, 0, 0, 1, // identity at t=0
0, 0.707, 0, 0.707, // 90deg Y at t=1
0, 0, 0, 1 // identity at t=2
]
);
const opacityTrack = new THREE.NumberKeyframeTrack(
'MyCube.material.opacity',
[0, 1, 2],
[1, 0.3, 1]
);
// Create clip from tracks
const clip = new THREE.AnimationClip('BounceAndSpin', 2, [
positionTrack,
rotationTrack,
opacityTrack
]);
// Set up mixer and play
const mixer = new THREE.AnimationMixer(cube);
const action = mixer.clipAction(clip);
action.play();
// IMPORTANT: enable transparency for opacity animation
material.transparent = true;
function animate() {
const delta = clock.getDelta();
mixer.update(delta);
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);---
Example 7: Morph Target Animation
Animate morph targets (blend shapes) loaded from GLTF.
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('face.glb', (gltf) => {
scene.add(gltf.scene);
mixer = new THREE.AnimationMixer(gltf.scene);
// GLTF morph target animations are stored as regular clips
// They target mesh.morphTargetInfluences[index]
const smileClip = THREE.AnimationClip.findByName(gltf.animations, 'Smile');
if (smileClip) {
const action = mixer.clipAction(smileClip);
action.play();
}
});
// Manual morph target control (alternative to clip-based)
function manualMorphControl(mesh, elapsed) {
if (mesh.morphTargetInfluences) {
mesh.morphTargetInfluences[0] = Math.sin(elapsed) * 0.5 + 0.5;
}
}
function animate() {
const delta = clock.getDelta();
if (mixer) mixer.update(delta);
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);threejs-impl-animation — Method Reference
Complete API signatures for the Three.js Animation System (r160+).
---
AnimationMixer
Constructor
new AnimationMixer(rootObject: Object3D): AnimationMixerProperties
mixer.time: number // Global mixer time in seconds (default: 0)
mixer.timeScale: number // Global speed multiplier (default: 1; 0 pauses all)Methods
mixer.clipAction(
clip: AnimationClip,
optionalRoot?: Object3D,
blendMode?: number
): AnimationAction
// Returns (or creates and caches) an AnimationAction for the given clip.
// blendMode: THREE.NormalAnimationBlendMode | THREE.AdditiveAnimationBlendMode
mixer.existingAction(
clip: AnimationClip,
optionalRoot?: Object3D
): AnimationAction | null
// Returns a previously created action or null.
mixer.update(deltaTimeInSeconds: number): AnimationMixer
// Advances the mixer. MUST call every frame with clock.getDelta().
mixer.setTime(timeInSeconds: number): AnimationMixer
// Sets the global mixer time and forces an update of all active actions.
mixer.stopAllAction(): AnimationMixer
// Deactivates all currently scheduled actions.
mixer.getRoot(): Object3D
// Returns the root object passed to the constructor.
mixer.uncacheAction(clip: AnimationClip, optionalRoot?: Object3D): void
// Deallocates the cached action for the given clip.
mixer.uncacheClip(clip: AnimationClip): void
// Deallocates all cached data for the given clip.
mixer.uncacheRoot(root: Object3D): void
// Deallocates all cached data for the given root object.Events
mixer.addEventListener('finished', (event: {
action: AnimationAction,
direction: number,
type: 'finished'
}) => void)
// Fires when an action with LoopOnce and clampWhenFinished=true completes.
mixer.addEventListener('loop', (event: {
action: AnimationAction,
loopDelta: number,
type: 'loop'
}) => void)
// Fires when an action completes a loop iteration.---
AnimationAction
Constructor (Internal -- NEVER call directly)
new AnimationAction(
mixer: AnimationMixer,
clip: AnimationClip,
localRoot?: Object3D,
blendMode?: number
): AnimationActionALWAYS create via mixer.clipAction(clip).
Properties
action.blendMode: number // NormalAnimationBlendMode | AdditiveAnimationBlendMode
action.clampWhenFinished: boolean // default: false
action.enabled: boolean // default: true
action.loop: number // LoopOnce | LoopRepeat | LoopPingPong (default: LoopRepeat)
action.paused: boolean // default: false
action.repetitions: number // default: Infinity
action.time: number // local playback time in seconds (default: 0)
action.timeScale: number // speed multiplier (default: 1; 0=pause, negative=reverse)
action.weight: number // blend influence [0, 1] (default: 1)
action.zeroSlopeAtEnd: boolean // default: true
action.zeroSlopeAtStart: boolean // default: truePlayback Methods
action.play(): AnimationAction
// Activates the action. Call ONCE to start playback.
action.stop(): AnimationAction
// Stops playback, resets time to 0, and deactivates the action.
action.reset(): AnimationAction
// Resets time=0, enabled=true, paused=false, timeScale=1, weight=1.
// Cancels any scheduled fading and warping. Does NOT deactivate.
action.startAt(time: number): AnimationAction
// Delays the start to the specified mixer time.Fading Methods
action.fadeIn(durationInSeconds: number): AnimationAction
// Fades weight from 0 to 1 over the specified duration.
action.fadeOut(durationInSeconds: number): AnimationAction
// Fades weight from current value to 0 over the specified duration.
action.crossFadeFrom(
fadeOutAction: AnimationAction,
durationInSeconds: number,
warpBoolean: boolean
): AnimationAction
// Crossfades from fadeOutAction into this action.
// If warp=true, also warps timeScale for smooth speed transition.
action.crossFadeTo(
fadeInAction: AnimationAction,
durationInSeconds: number,
warpBoolean: boolean
): AnimationAction
// Crossfades from this action to fadeInAction.
action.stopFading(): AnimationAction
// Cancels any currently active fade.Speed and Timing Methods
action.halt(durationInSeconds: number): AnimationAction
// Decelerates timeScale to 0 over the specified duration.
action.warp(
startTimeScale: number,
endTimeScale: number,
durationInSeconds: number
): AnimationAction
// Smoothly transitions playback speed from start to end over duration.
action.stopWarping(): AnimationAction
// Cancels any currently active warp.
action.setDuration(durationInSeconds: number): AnimationAction
// Sets timeScale so one loop takes exactly durationInSeconds.
action.setEffectiveTimeScale(timeScale: number): AnimationAction
// Sets effective time scale (considers paused state).
action.setEffectiveWeight(weight: number): AnimationAction
// Sets effective weight (considers enabled state).
action.setLoop(mode: number, repetitions: number): AnimationAction
// Sets loop mode and repetition count.
action.syncWith(otherAction: AnimationAction): AnimationAction
// Synchronizes this action's time with another action.Query Methods
action.isRunning(): boolean
// true only when action is actively playing (not paused, weight > 0, speed != 0).
action.isScheduled(): boolean
// true if .play() was called and action has not been stopped.
action.getClip(): AnimationClip
action.getMixer(): AnimationMixer
action.getRoot(): Object3D
action.getEffectiveTimeScale(): number
action.getEffectiveWeight(): number---
AnimationClip
Constructor
new AnimationClip(
name?: string, // default: ''
duration?: number, // default: -1 (auto-calculate)
tracks?: KeyframeTrack[],
blendMode?: number
): AnimationClipProperties
clip.blendMode: number
clip.duration: number // length in seconds
clip.name: string // identifier
clip.tracks: KeyframeTrack[] // keyframe data
clip.userData: Object // custom metadata
clip.uuid: string // readonly unique IDInstance Methods
clip.clone(): AnimationClip
clip.optimize(): AnimationClip // removes redundant sequential keys
clip.resetDuration(): AnimationClip // updates duration to longest track
clip.toJSON(): Object
clip.trim(): AnimationClip // crops all tracks to clip duration
clip.validate(): boolean // returns true if all tracks are validStatic Methods
AnimationClip.findByName(
objectOrClipArray: Object3D | AnimationClip[],
name: string
): AnimationClip
// Finds a clip by name in an array or on an object's .animations property.
AnimationClip.CreateFromMorphTargetSequence(
name: string,
morphTargetSequence: MorphTarget[],
fps: number,
noLoop: boolean
): AnimationClip
AnimationClip.CreateClipsFromMorphTargetSequences(
morphTargets: MorphTarget[],
fps: number,
noLoop: boolean
): AnimationClip[]
AnimationClip.parse(json: Object): AnimationClip
AnimationClip.toJSON(clip: AnimationClip): Object---
KeyframeTrack
Base Constructor
new KeyframeTrack(
name: string, // PropertyBinding path (e.g., "mesh.position")
times: Float32Array | number[], // keyframe times in seconds
values: Float32Array | number[], // keyframe values
interpolation?: number // InterpolateDiscrete | InterpolateLinear | InterpolateSmooth
): KeyframeTrackSpecialized Track Constructors
new VectorKeyframeTrack(name, times, values, interpolation?)
new QuaternionKeyframeTrack(name, times, values, interpolation?)
new NumberKeyframeTrack(name, times, values, interpolation?)
new BooleanKeyframeTrack(name, times, values) // discrete only
new ColorKeyframeTrack(name, times, values, interpolation?)
new StringKeyframeTrack(name, times, values) // discrete onlyTrack Methods
track.optimize(): KeyframeTrack // removes redundant sequential keys
track.scale(timeScale: number): KeyframeTrack
track.shift(timeOffset: number): KeyframeTrack
track.trim(startTime: number, endTime: number): KeyframeTrack
track.clone(): KeyframeTrack
track.validate(): boolean---
Clock
Constructor
new Clock(autoStart?: boolean): Clock
// autoStart defaults to true.Properties
clock.autoStart: boolean
clock.elapsedTime: number // total accumulated time in seconds
clock.oldTime: number // last method call timestamp
clock.running: boolean // default: true
clock.startTime: number // when start() was last calledMethods
clock.getDelta(): number // seconds since last getDelta() call
clock.getElapsedTime(): number // total elapsed time in seconds
clock.start(): void // starts the clock
clock.stop(): void // halts without resetting---
Constants
// Loop modes
THREE.LoopOnce // plays once and stops
THREE.LoopRepeat // loops, restarting from beginning (default)
THREE.LoopPingPong // alternates forward/backward
// Blend modes
THREE.NormalAnimationBlendMode // standard blending (default)
THREE.AdditiveAnimationBlendMode // additive layering
// Interpolation modes
THREE.InterpolateDiscrete // step function
THREE.InterpolateLinear // linear (default)
THREE.InterpolateSmooth // cubic spline