<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
Section titled “Theatre.js Docs”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_"]}Lifecycle
Section titled “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.
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
Section titled “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>