threlte logo
@threlte/theatre

<Sequence>

Sequences are the heart of the Theatre.js animation system. The sequence represents the animation timeline and provides an API for controlling its playback. In Threlte 7, you can reactively control animations through the <Sequence> component, which you place inside a <Sheet>.

Currently, you can only have one sequence in each sheet. Future versions of Theatre.js are expected to support multisequence sheets.

Theatre.js Docs

   
SequenceSequence ManualSequence API Reference

Usage

The following example shows how <Sequence> can be used to build a simple playback controller.

<script lang="ts">
  import { Canvas } from '@threlte/core'
  import { Project, Sequence, Sheet, type SequenceController } from '@threlte/theatre'
  import Controller from './Controller.svelte'
  import Scene from './Scene.svelte'
  import state from './state.json'

  let sequence: SequenceController

  let position: number = 0
  let playing: boolean = false
  let play: (opts?: {}) => Promise<boolean>
  let pause: (opts?: {}) => Promise<boolean>
  let rate: number = 1
</script>

<div>
  <Canvas>
    <Project config={{ state }}>
      <Sheet>
        <Scene />
        <Sequence
          bind:sequence
          bind:playing
          bind:position
          bind:play
          bind:pause
          iterationCount={3}
          direction="alternate"
          autoplay
          delay={1000}
          {rate}
        />
      </Sheet>
    </Project>
  </Canvas>
  <Controller
    bind:position
    bind:playing
    bind:rate
    {play}
    {pause}
  />
</div>

<style>
  div {
    height: 100%;
  }
</style>
<script lang="ts">
  export let position: number
  export let playing: boolean
  export let play: (opts?: {}) => Promise<boolean>
  export let pause: (opts?: {}) => Promise<boolean>
  export let rate: number = 1

  import { PauseIcon, PlayIcon } from './icons'

  const fmt = (n: number) =>
    n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })

  const toggleRate = () => {
    if (rate == 1) {
      rate = 0.5
    } else if (rate == 0.5) {
      rate = 2
    } else {
      rate = 1
    }
  }
</script>

<menu>
  {#if !playing}
    <button on:click={() => play()}>
      <PlayIcon />
    </button>
  {:else}
    <button on:click={() => pause()}>
      <PauseIcon />
    </button>
  {/if}
  <button on:click={() => toggleRate()}>
    x{rate.toFixed(1)}
  </button>
  <input
    type="range"
    min={0}
    max={1}
    step={0.01}
    bind:value={position}
  />
  <div>{fmt(position)}</div>
</menu>

<style>
  menu {
    position: absolute;
    top: 0;
    width: 100%;
    display: flex;
    flex-flow: row;
    align-items: center;
    gap: 1rem;
    padding: 1rem;
    color: white;
    font-weight: bold;
  }

  menu :global(svg) {
    fill: white;
    width: 24px;
    height: 24px;
  }

  menu div {
    width: 50px;
    min-width: 50px;
  }

  menu input {
    flex-grow: 1;
  }
</style>
<script lang="ts">
  import { T } from '@threlte/core'
  import { SheetObject } from '@threlte/theatre'
</script>

<!-- Box -->
<SheetObject key="Box">
  {#snippet children({ Transform, Sync })}
    <Transform>
      <T.Mesh
        receiveShadow
        castShadow
      >
        <T.BoxGeometry args={[1, 1, 1]} />
        <T.MeshStandardMaterial>
          <Sync
            color
            emissive
          />
        </T.MeshStandardMaterial>
      </T.Mesh>
    </Transform>
  {/snippet}
</SheetObject>

<T.DirectionalLight
  position={[0.5, 2, 1]}
  castShadow
/>

<T.AmbientLight intensity={0.2} />

<T.PerspectiveCamera
  position={[4, 5, 10]}
  makeDefault
  oncreate={(ref) => {
    ref.lookAt(0, 0.5, 0)
  }}
/>
<svg
  xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 256 256"
>
  <rect
    width="256"
    height="256"
    fill="none"
  />
  <path
    d="M216,48V208a16,16,0,0,1-16,16H160a16,16,0,0,1-16-16V48a16,16,0,0,1,16-16h40A16,16,0,0,1,216,48ZM96,32H56A16,16,0,0,0,40,48V208a16,16,0,0,0,16,16H96a16,16,0,0,0,16-16V48A16,16,0,0,0,96,32Z"
  />
</svg>
<svg
  xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 256 256"
>
  <rect
    width="256"
    height="256"
    fill="none"
  />
  <path
    d="M240,128a15.74,15.74,0,0,1-7.6,13.51L88.32,229.65a16,16,0,0,1-16.2.3A15.86,15.86,0,0,1,64,216.13V39.87a15.86,15.86,0,0,1,8.12-13.82,16,16,0,0,1,16.2.3L232.4,114.49A15.74,15.74,0,0,1,240,128Z"
  />
</svg>
export { default as PlayIcon } from './PlayIcon.svelte'
export { default as PauseIcon } from './PauseIcon.svelte'
{
  "sheetsById": {
    "default": {
      "staticOverrides": {
        "byObject": {
          "Box": {
            "scale": {
              "x": 1,
              "y": 1,
              "z": 1
            },
            "rotation": {
              "x": 0,
              "y": 0,
              "z": 0
            },
            "color": {
              "r": 1,
              "g": 1,
              "b": 1,
              "a": 1
            }
          }
        }
      },
      "sequence": {
        "subUnitsPerUnit": 30,
        "length": 1,
        "type": "PositionalSequence",
        "tracksByObject": {
          "Box": {
            "trackData": {
              "6jxcIVb5VE": {
                "type": "BasicKeyframedTrack",
                "__debugName": "Box:[\"position\",\"x\"]",
                "keyframes": [
                  {
                    "id": "2znog1Hqt9",
                    "position": 0,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 0
                  },
                  {
                    "id": "izysi9xIZV",
                    "position": 0.467,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 0
                  },
                  {
                    "id": "1R7ALr_y7I",
                    "position": 1,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 0
                  }
                ]
              },
              "CL6ilYk5F0": {
                "type": "BasicKeyframedTrack",
                "__debugName": "Box:[\"position\",\"y\"]",
                "keyframes": [
                  {
                    "id": "ILuids3SS2",
                    "position": 0,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 0
                  },
                  {
                    "id": "5Fx9QwZog4",
                    "position": 0.467,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 2.884894266415041
                  },
                  {
                    "id": "QdxaWbUKTO",
                    "position": 1,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 0
                  }
                ]
              },
              "k17VolLHaa": {
                "type": "BasicKeyframedTrack",
                "__debugName": "Box:[\"position\",\"z\"]",
                "keyframes": [
                  {
                    "id": "ensBkfE4zJ",
                    "position": 0,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 0
                  },
                  {
                    "id": "qGYRgJFQt0",
                    "position": 0.467,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 0
                  },
                  {
                    "id": "acTy6onWqA",
                    "position": 1,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 0
                  }
                ]
              },
              "8ozF3Q9pr9": {
                "type": "BasicKeyframedTrack",
                "__debugName": "Box:[\"rotation\",\"x\"]",
                "keyframes": [
                  {
                    "id": "zemdW99tC7",
                    "position": 0.3,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 0
                  },
                  {
                    "id": "FKeku-J_jh",
                    "position": 0.7,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 90
                  }
                ]
              },
              "Yz64RYOUhH": {
                "type": "BasicKeyframedTrack",
                "__debugName": "Box:[\"rotation\",\"y\"]",
                "keyframes": [
                  {
                    "id": "cO8d4gOyP7",
                    "position": 0.3,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 0
                  },
                  {
                    "id": "GEPzCyTP8m",
                    "position": 0.7,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": 90
                  }
                ]
              },
              "aWuU3nv7t2": {
                "type": "BasicKeyframedTrack",
                "__debugName": "Box:[\"rotation\",\"z\"]",
                "keyframes": []
              },
              "825UyDpz6a": {
                "type": "BasicKeyframedTrack",
                "__debugName": "Box:[\"emissive\"]",
                "keyframes": [
                  {
                    "id": "2u6ASUPCLN",
                    "position": 0,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": {
                      "r": 0,
                      "g": 0,
                      "b": 0,
                      "a": 1
                    }
                  },
                  {
                    "id": "NuH1fhxmZy",
                    "position": 0.467,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": {
                      "r": 0.996078431372549,
                      "g": 0.23921568627450981,
                      "b": 0,
                      "a": 1
                    }
                  },
                  {
                    "id": "VmWNCbzBhT",
                    "position": 1,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": {
                      "r": 0,
                      "g": 0,
                      "b": 0,
                      "a": 1
                    }
                  }
                ]
              },
              "UP-UOQISBD": {
                "type": "BasicKeyframedTrack",
                "__debugName": "Box:[\"color\"]",
                "keyframes": [
                  {
                    "id": "A-rHMw_1mz",
                    "position": 0,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": {
                      "r": 1,
                      "g": 1,
                      "b": 1,
                      "a": 1
                    }
                  },
                  {
                    "id": "jrpD3O8bLK",
                    "position": 0.467,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": {
                      "r": 0.996078431372549,
                      "g": 0.23921568627450981,
                      "b": 0,
                      "a": 1
                    }
                  },
                  {
                    "id": "3B_Qi4_QFS",
                    "position": 1,
                    "connectedRight": true,
                    "handles": [0.5, 1, 0.5, 0],
                    "type": "bezier",
                    "value": {
                      "r": 1,
                      "g": 1,
                      "b": 1,
                      "a": 1
                    }
                  }
                ]
              }
            },
            "trackIdByPropPath": {
              "[\"position\",\"x\"]": "6jxcIVb5VE",
              "[\"position\",\"y\"]": "CL6ilYk5F0",
              "[\"position\",\"z\"]": "k17VolLHaa",
              "[\"rotation\",\"x\"]": "8ozF3Q9pr9",
              "[\"rotation\",\"y\"]": "Yz64RYOUhH",
              "[\"rotation\",\"z\"]": "aWuU3nv7t2",
              "[\"emissive\"]": "825UyDpz6a",
              "[\"color\"]": "UP-UOQISBD"
            }
          }
        }
      }
    }
  },
  "definitionVersion": "0.4.0",
  "revisionHistory": ["VSRORcDV9_yJ8fbi", "4poCDvFG3GZwc5nU", "gR-SIAqfZWUtx-q_"]
}

Lifecycle

Threlte provides lifecycle props to allow you to configure how the sequence playback is connected to the Svelte component lifecycle. See the autoplay, autoreset and autopause props below.

Note that the underlying Theatre.js sheets are persisted even when unmounting a <Sheet> component. That’s why the sequence doesn’t reset automatically when unmounting a <Sheet>, and why the autoreset options is required.

Audio

The audio options allow you to attach a soundtrack to your animation sequence. Theatre.js achieves this using the Web Audio API. For more details, see audio manual and attach audio API reference

Snippet Prop

When using the sequence in a child component, a snippet prop can come in handy.

<script lang="ts">
  import { T } from '@threlte/core'
  import { Sheet, Sequence, SheetObject } from '@threlte/theatre'
</script>

<Sheet>
  <Sequence>
    {#snippet children({ play })}
      <SheetObject key="Cube">
        {#snippet children({ Transform })}
          <Transform>
            <T.Mesh onclick={play}>
              <T.BoxGeometry />
              <T.MeshStandardMaterial />
            </T.Mesh>
          </Transform>
        {/snippet}
      </SheetObject>
    {/snippet}
  </Sequence>
</Sheet>

Component Signature

Props

name
type
required
default
description

audio
{source: AudioBuffer; audioContext: AudioContext; destinationNode: AudioContext['destination'];}
no
{}
Syncronize an audio track to the sequence; see the audio section below (Theatre.js)

autopause
boolean
no
false
Whether to pause playback when the component is unmounted (Threlte)

autoplay
boolean
no
false
Choose whether to automatically play the animation when the component is mounted (Threlte)

autoreset
"always" | "onMount" | "onDestroy" | undefined
no
undefined
Reset the playhead when the component is mounted, unmounted**, both or neither (Threlte)

delay
number
no
0
When using autoplay, how many milliseconds to wait before starting playback (Threlte)

direction
"normal" | "reverse" | "alternate" | "alternateReverse"
no
normal
Choose the direction of animation playback (Theatre.js)

iterationCount
number
no
1
Control how often the animation is played. Set Infinity to keep looping (Theatre.js)

range
[number, number] | undefined
no
[0, length]
Choose what part of the animation is played (Theatre.js)

rate
number
no
1
Set the speed of playback (Theatre.js)

Bindings

name
type

position
number | undefined

play
(opts?) => Promise<boolean> (see Theatre.js Sequence API docs for options)

pause
() => void

sequence
ISequence

sheet
ISheet