Initialize Three.js AnimationMixer for Character Animation

This article provides a step-by-step guide on how to properly initialize and use the AnimationMixer in Three.js to play complex character animations. You will learn how to bind the mixer to a 3D model, load animation clips, trigger playback using animation actions, and correctly update the mixer within the rendering loop to achieve smooth transitions.

Step 1: Load the Model and Instantiate the Mixer

To animate a character, you first need a rigged 3D model (typically in GLTF/GLB format) that contains embedded skeleton animations. When the loader successfully imports the model, you initialize the AnimationMixer by passing the root object of the model to the mixer’s constructor.

import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';

let mixer;
const loader = new GLTFLoader();

loader.load('character.glb', (gltf) => {
    const model = gltf.scene;
    scene.add(model);

    // Initialize the AnimationMixer with the root model object
    mixer = new THREE.AnimationMixer(model);
});

Step 2: Create and Play Animation Actions

The loaded GLTF object contains an array of AnimationClip objects inside gltf.animations. To play these animations, you must convert the clips into controllable AnimationAction objects using the mixer’s clipAction method.

// Access the animation clips array from the loaded GLTF
const clips = gltf.animations;

// Find a specific clip by name (e.g., "Idle" or "Run")
const idleClip = THREE.AnimationClip.findByName(clips, 'Idle');

// Create an AnimationAction for the clip
const idleAction = mixer.clipAction(idleClip);

// Play the animation action
idleAction.play();

Step 3: Update the Mixer in the Render Loop

The AnimationMixer requires manual updates to progress the animation frames. You must calculate the time elapsed between frames (delta time) using a THREE.Clock and pass it to the mixer’s update method inside your requestAnimationFrame loop.

const clock = new THREE.Clock();

function animate() {
    requestAnimationFrame(animate);

    // Get the delta time in seconds
    const delta = clock.getDelta();

    // Update the animation mixer
    if (mixer) {
        mixer.update(delta);
    }

    renderer.render(scene, camera);
}

animate();

Step 4: Handle Complex Animations and Blending

For complex character states (like transitioning from walking to running), you can smoothly blend animations. Three.js allows you to crossfade actions using the crossFadeTo method, which prevents abrupt visual snapping.

let currentAction = idleAction;

function transitionTo(nextAction, duration = 0.5) {
    if (currentAction === nextAction) return;

    // Reset the next action so it starts from the beginning
    nextAction.reset();
    nextAction.play();
    nextAction.setEffectiveWeight(1);

    // Crossfade from the current action to the next action
    currentAction.crossFadeTo(nextAction, duration, true);

    // Update the current action reference
    currentAction = nextAction;
}