Introduction
MotionActor runs a TypeScript generator once, records the motion it describes, and reads any frame of that record on demand.
MotionActor is a TypeScript runtime for video. A scene is a generator that spawns actors, tweens their state and waits. The runtime runs it once and keeps a log of everything it did; any frame is then read straight from that log, and a renderer draws what it reads.
The player is running the files beside it, and the scene’s running line lights as it plays: the fade takes 12 frames, the wait 15 and the slide 18, so the scene is 45 frames long when the player builds it at 30 fps. Drag the playhead backwards and the title slides back, because every frame is computed from the log on its own.
Compile, resolve, render
createScenedeclares the scene and runs nothing. A host’sscene.build({ viewport, fps })runs the generator and records each spawn, tween and wait. Durations are written in seconds and become whole frames at the build’sfps.compiled.at(frame)rebuilds every actor’s state at one frame from the record. The same frame always gives the same state.- A renderer takes that state and draws it. MotionActor has backends for the DOM, Pixi, Three.js and Skia, and plays in Remotion, HyperFrames or its own preview and exporter.
Resolving is plain TypeScript, so the state of any frame is a typed value you can read without drawing anything:
import { const scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
title: Title;
}>
scene } from "./intro.scene";
using using compiled: CompiledScene<{
title: Title;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled = const scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
title: Title;
}>
scene.SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
title: Title;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1280, SceneViewport.height: numberheight: 720 }, fps: numberfps: 30 });
const const title: ResolvedActorOfInstance<Title> | undefinedtitle = using compiled: CompiledScene<{
title: Title;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.at(frame: Frame, options?: FrameResolutionOptions): FrameState<{
title: Title;
}>
at(36).FrameState<{ ...; }>.scene: {
title: ResolvedActorOfInstance<Title> | undefined;
}
scene.title: ResolvedActorOfInstance<Title> | undefinedtitle;
const title: ResolvedActorOfInstance<Title> | undefinedtitle?.layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
}
layout.x: LayoutValue | undefinedx; // 480, halfway through the slide
const title: ResolvedActorOfInstance<Title> | undefinedtitle?.appearance: {
opacity: number;
}
appearance.opacity: number | undefinedopacity; // 1
using compiled: CompiledScene<{
title: Title;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.totalDuration: numbertotalDuration; // 45
What a pure frame buys
Seeking is one call. Frame 900 costs the same whether or not anything asked for frame 899, so a scrubber, a thumbnail strip and an editor’s timeline all read the scene the same way.
Export runs in parallel. The exporter can mount several copies of a composition and give each the next frame nobody has taken. When a frame resolves to the same state as the one before it, the exporter repeats the previous picture instead of capturing again.
Scenes are testable. A unit test can assert where an actor is at frame 90 with no browser, canvas or player involved.
Quickstart
How it works
Scenes
Reactivity
Rendering
Examples