Three.js Animation: AnimationMixer, Tweening & GSAP

Three.js animates two different things in two different ways: imported skeletal/keyframe animations (from a glTF model) play through THREE.AnimationMixer, while simple property animation (moving a camera, fading an opacity, easing a rotation) is usually handled with a tweening library like GSAP rather than Three.js's own animation system, since GSAP's easing and timeline API is considerably more ergonomic for that use case.

Last updated . Verified against three.js r181.

Read the FAQJump to code

Playing imported animations with AnimationMixer

When GLTFLoader loads a model that includes animations (a walk cycle, an idle loop), they arrive as AnimationClip objects in gltf.animations. AnimationMixer plays them: create one mixer per animated object, get an AnimationAction from a clip, and call .play(). The mixer must be advanced every frame with mixer.update(delta). Forgetting this call is the most common "my imported animation doesn't play" bug.

Multiple actions can cross-fade (action.crossFadeTo(otherAction, duration)) for smooth transitions between animation states (walk to run, idle to jump) rather than an abrupt cut.

Simple property animation: manual vs GSAP

For animating a property directly (a camera flying to a new position, an object fading in, a UI-driven rotation) you have two practical options:

  • Manual, in the render loop: track progress with clock.getDelta() or a start time, apply an easing function yourself. Full control, but you're reimplementing easing curves, sequencing and interruption handling that a library already solves well.
  • GSAP: the de facto standard for this in the Three.js community. gsap.to(mesh.position, { x: 5, duration: 1, ease: 'power2.out' }) handles easing, sequencing (timelines), and interruption (starting a new tween on an object mid-animation) far more robustly than a hand-rolled solution.

The two systems compose fine in the same project: AnimationMixer for imported clips, GSAP for everything else.

Animation performance notes

Each active AnimationMixer costs CPU time every frame proportional to the number of animated bones/properties. For scenes with many simultaneously animated characters, consider capping how many mixers update per frame for off-screen or distant objects (frustum-based culling of animation updates, not just rendering). GSAP tweens are generally cheap individually, but hundreds of concurrent tweens targeting the same object can conflict. GSAP handles overwrite behavior configurably, which is worth understanding before it causes visually janky fights between competing tweens.

Tools and libraries

Code

AnimationMixer for imported clips (r181)
1let mixer
2loader.load('/models/character.glb', (gltf) => {
3 scene.add(gltf.scene)
4 mixer = new THREE.AnimationMixer(gltf.scene)
5 const walkAction = mixer.clipAction(gltf.animations[0])
6 walkAction.play()
7})
8
9const clock = new THREE.Clock()
10renderer.setAnimationLoop(() => {
11 if (mixer) mixer.update(clock.getDelta()) // easy to forget, required every frame
12 renderer.render(scene, camera)
13})
GSAP for a camera fly-in
1import gsap from 'gsap'
2
3gsap.to(camera.position, {
4 x: 0, y: 2, z: 5,
5 duration: 1.5,
6 ease: 'power3.out',
7 onUpdate: () => camera.lookAt(0, 0, 0),
8})
9// GSAP's timeline API also sequences multiple tweens declaratively:
10gsap.timeline()
11 .to(mesh.rotation, { y: Math.PI, duration: 1 })
12 .to(mesh.scale, { x: 1.2, y: 1.2, z: 1.2, duration: 0.3 }, '-=0.2')

Learn this properly

Three.js + GSAP

Why GSAP Changes Everything

The dedicated GSAP + Three.js course covers this integration in depth.

Start the lesson (8 minutes)

Frequently asked questions

Why doesn't my imported Three.js animation play?

The most common cause is forgetting to call mixer.update(delta) every frame in your render loop. Creating the AnimationMixer and calling .play() on an action isn't enough; the mixer must be advanced manually each frame.

Should I use GSAP or Three.js's own animation system?

Use AnimationMixer for imported glTF animations (skeletal/keyframe clips); there's no alternative for those. For simple property tweening (position, rotation, opacity), GSAP is the community standard and considerably more ergonomic than hand-rolled easing code.

Can I mix GSAP and AnimationMixer in the same Three.js project?

Yes, and it's common: use AnimationMixer for imported character/object animations and GSAP for everything else (camera moves, UI-driven transitions), with no conflict between them.

Keep reading