Adjust Three.js Animation Timescale Dynamically

This article explains how to dynamically adjust the playback speed of an ongoing animation in Three.js using the AnimationAction.timeScale property. You will learn how to access the active animation action, modify its speed in real-time, and smoothly transition between different playback speeds to create seamless visual effects.

Understanding the AnimationAction timeScale

In Three.js, animations are managed by an AnimationMixer, which plays back AnimationClip resources using AnimationAction objects. The playback rate of any active AnimationAction is controlled by its timeScale property.

The default value of timeScale is 1.0, which represents normal playback speed. * A value of 2.0 doubles the speed. * A value of 0.5 halves the speed. * A value of 0.0 pauses the animation. * Negative values (e.g., -1.0) play the animation backward.

Directly Modifying the Speed

To change the speed of an animation instantly, obtain a reference to your AnimationAction and set its timeScale property directly.

// Initialize the mixer and get the animation action
const mixer = new THREE.AnimationMixer(characterMesh);
const walkClip = THREE.AnimationClip.findByName(clips, 'Walk');
const walkAction = mixer.clipAction(walkClip);

// Play the animation
walkAction.play();

// Dynamically change the speed to double-time
walkAction.timeScale = 2.0;

// Dynamically change the speed to slow-motion
walkAction.timeScale = 0.25;

This change takes effect immediately on the next frame update within your requestAnimationFrame loop where mixer.update(deltaTime) is called.

Transitioning Speed Smoothly

Abruptly changing the animation speed can look unnatural. To smoothly transition between two speeds, you can interpolate the timeScale value inside your application’s update loop.

Here is how to implement a smooth linear interpolation (lerp) for the speed adjustment:

let targetTimeScale = 1.0;
const transitionSpeed = 0.05; // Control how fast the speed adjusts

function update() {
    requestAnimationFrame(update);

    const clockDelta = clock.getDelta();

    // Linearly interpolate the current timeScale toward the targetTimeScale
    if (walkAction.timeScale !== targetTimeScale) {
        walkAction.timeScale = THREE.MathUtils.lerp(
            walkAction.timeScale,
            targetTimeScale,
            transitionSpeed
        );
    }

    // Update the animation mixer
    mixer.update(clockDelta);
    renderer.render(scene, camera);
}

// Example trigger: Slow down animation when user holds a key
window.addEventListener('keydown', (e) => {
    if (e.code === 'Space') {
        targetTimeScale = 0.2; // Smoothly slow down to 20% speed
    }
});

window.addEventListener('keyup', (e) => {
    if (e.code === 'Space') {
        targetTimeScale = 1.0; // Smoothly return to normal speed
    }
});

Using this approach ensures that transitions between fast running, walking, and slow-motion states look fluid and natural.