Skip to content

<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.

Sequence | Sequence Manual | Sequence API Reference

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 stateJson from './state.json'
let sequence = $state<SequenceController>()
let position = $state(0)
let playing = $state(false)
let play = $state<(opts?: {}) => Promise<boolean>>()
let pause = $state<(opts?: {}) => Promise<boolean>>()
let rate = $state(1)
</script>
<div>
<Canvas>
<Project config={{ state: stateJson }}>
<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:rate
{playing}
{play}
{pause}
/>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import { PauseIcon, PlayIcon } from './icons'
interface Props {
position: number
playing: boolean
play?: (opts?: {}) => Promise<boolean>
pause?: (opts?: {}) => Promise<boolean>
rate?: number
}
let { position = $bindable(), playing, play, pause, rate = $bindable(1) }: Props = $props()
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 onclick={() => play?.()}>
<PlayIcon />
</button>
{:else}
<button onclick={() => pause?.()}>
<PauseIcon />
</button>
{/if}
<button onclick={() => 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_"]
}

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.

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

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>

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