Skip to content

Animation transitions

Transition seamlessly between glTF animations.

<script lang="ts">
import { Pane, Button } from 'svelte-tweakpane-ui'
import { Canvas } from '@threlte/core'
import Scene from './Scene.svelte'
import type { CharacterActions } from './types'
let action = $state<CharacterActions>('idle')
</script>
<Pane
title="Transitions"
position="fixed"
>
<Button
title="Idle"
on:click={() => {
action = 'idle'
}}
/>
<Button
title="Walk"
on:click={() => {
action = 'walk'
}}
/>
<Button
title="Run"
on:click={() => {
action = 'run'
}}
/>
</Pane>
<div>
<Canvas>
<Scene {action} />
</Canvas>
</div>
<style>
div {
position: relative;
height: 100%;
width: 100%;
}
</style>
<script lang="ts">
import { GLTF, useGltfAnimations } from '@threlte/extras'
import type { CharacterActions } from './types'
type Props = {
actionKey: CharacterActions
}
let { actionKey = 'idle' }: Props = $props()
let { gltf, actions } = useGltfAnimations()
let currentActionKey: CharacterActions = 'idle'
$effect(() => {
// This effect acts like an init default pose
$actions?.['idle']?.play()
})
$effect(() => {
transitionTo(actionKey, 0.3)
})
function transitionTo(actionKey: CharacterActions, duration = 1) {
const currentAction = $actions[currentActionKey]
const nextAction = $actions[actionKey]
if (!nextAction || currentAction === nextAction) return
// Function inspired by: https://github.com/mrdoob/three.js/blob/master/examples/webgl_animation_skinning_blending.html
nextAction.enabled = true
if (currentAction) {
currentAction.crossFadeTo(nextAction, duration, true)
}
// Not sure why I need this but the source code does not
nextAction.play()
currentActionKey = actionKey
}
</script>
<GLTF
bind:gltf={$gltf}
url="https://threejs.org/examples/models/gltf/Xbot.glb"
oncreate={(scene) => {
scene.traverse((child) => {
child.castShadow = true
})
}}
/>
<script lang="ts">
import { MathUtils } from 'three'
import { T } from '@threlte/core'
import Character from './Character.svelte'
let { action } = $props()
</script>
<T.PerspectiveCamera
makeDefault
position={[-0.85, 1.75, 2.46]}
oncreate={(ref) => {
ref.lookAt(0, 1, 0)
}}
/>
<T.AmbientLight />
<T.DirectionalLight
position={[10, 5, 5]}
castShadow
/>
<Character actionKey={action} />
<T.Mesh
rotation.x={MathUtils.degToRad(-90)}
receiveShadow
>
<T.CircleGeometry args={[3, 72]} />
<T.MeshStandardMaterial color={'white'} />
</T.Mesh>
export type CharacterActions =
| 'agree'
| 'headShake'
| 'idle'
| 'run'
| 'sad_pose'
| 'sneak_pose'
| 'walk'

glTF is a comprehensive file format for 3D models, and it supports animations. In this example, we extract the animations from the glTF file, play them, and crossfade between them.

  1. Extract the variables gltf and actions.
const { gltf, actions } = useGltfAnimations()
  1. Bind gltf to the <GLTF> component.
<GLTF
bind:gltf={$gltf}
url="https://threejs.org/examples/models/gltf/Xbot.glb"
/>

This causes actions to be populated with an array of the available animations in that glTF file.

Run console.log(Object.entries($actions)) to see the available action strings and the shape of the animation object. By doing this, you’ll discover that this example is only using 3 of the 7 available animations attached to this glTF file.

  1. selecting a different animation calls transitionTo function, which crossfades between the two animations