threlte logo
@threlte/extras

useGltfAnimations

A convenience hook to use gltf animations loaded by a <GLTF> component or by the useGltf hook.

<script lang="ts">
  import Scene from './Scene.svelte'
  import { Button, Folder, Pane } from 'svelte-tweakpane-ui'
  import { Canvas } from '@threlte/core'

  let scene = $state.raw<Scene>()
  let animating = $state(false)

  const actions = $derived(scene?.actions)
  const action = $derived($actions?.['Take 001'])

  // start animating as soon as the action is ready
  $effect(() => {
    action?.play()
    animating = true
  })
</script>

<Pane
  position="fixed"
  title="littlest tokyo"
>
  <Folder title="animation">
    <Button
      disabled={animating}
      on:click={() => {
        action?.play()
        animating = true
      }}
      title="play"
    />
    <Button
      disabled={!animating}
      on:click={() => {
        action?.stop()
        animating = false
      }}
      title="stop"
    />
    <Button
      disabled={!animating}
      on:click={() => {
        action?.reset()
      }}
      title="reset"
    />
  </Folder>
</Pane>

<div>
  <Canvas>
    <Scene bind:this={scene} />
  </Canvas>
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  import { Environment, OrbitControls, useDraco, useGltf, useGltfAnimations } from '@threlte/extras'
  import { T } from '@threlte/core'

  const dracoLoader = useDraco()
  const gltf = useGltf('/models/LittlestTokyo.glb', { dracoLoader })

  export const { actions, mixer } = useGltfAnimations<'Take 001'>(() => $gltf)
</script>

<T.PerspectiveCamera
  makeDefault
  position={[600, 200, -600]}
  near={10}
  far={10_000}
>
  <OrbitControls
    autoRotate
    autoRotateSpeed={0.2}
    enableDamping
    enableZoom={false}
    target={[-60, -75, 0]}
  />
</T.PerspectiveCamera>

<Environment
  url="/textures/equirectangular/hdr/industrial_sunset_puresky_1k.hdr"
  isBackground
/>

{#await gltf then { scene }}
  <T is={scene} />
{/await}

Model: Littlest Tokyo by Glen Fox, CC Attribution.

Examples

useGltfAnimations takes the gltf as a getter so that reads are tracked through runes automatically. The hook returns the mixer and an actions store keyed by clip name — type the clip names with a generic to get autocompletion.

With the <GLTF> component

Bind a local $state variable to the <GLTF> component’s gltf prop and pass the getter to useGltfAnimations.

<script lang="ts">
  import { GLTF, useGltfAnimations, type ThrelteGltf } from '@threlte/extras'

  let gltf = $state<ThrelteGltf>()
  const { actions, mixer } = useGltfAnimations<'All Animations'>(() => gltf)
  mixer.timeScale = 0.5

  $effect(() => {
    $actions['All Animations']?.play()
  })
</script>

<GLTF
  url="/path/to/model.glb"
  bind:gltf
/>

With the useGltf hook

When you want to reuse parts of the GLTF such as materials, nodes, or the embedded camera, load it with useGltf and pass the value through the getter.

<script lang="ts">
  import { T } from '@threlte/core'
  import { useGltfAnimations, useGltf } from '@threlte/extras'

  const gltf = useGltf('/path/to/model.glb')
  const { actions, mixer } = useGltfAnimations<'All Animations'>(() => $gltf)

  $effect(() => {
    $actions['All Animations']?.play()
  })
</script>

{#await gltf then { scene }}
  <T is={scene} />
{/await}

Applying Animations to a Different Root

useGltfAnimations’s second optional argument is a getter for an alternative root Object3D. Use this when you want to retarget the clips onto a separate rig rather than the gltf’s own scene.

<script lang="ts">
  import { T } from '@threlte/core'
  import { useGltfAnimations, useGltf } from '@threlte/extras'
  import { Group } from 'three'

  const gltf = useGltf('/path/to/model.glb')

  const group = new Group()

  const { actions } = useGltfAnimations(
    () => $gltf,
    () => group
  )
</script>

{#await gltf then { scene }}
  <T is={group}>
    <T is={scene} />
  </T>
{/await}