---
title: Introduction
description: MotionActor runs a TypeScript generator once, records the motion it describes, and reads any frame of that record on demand.
mode: wide
---

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.

<IntroScene />

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

1. `createScene` declares the scene and runs nothing. A host's `scene.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's `fps`.
2. `compiled.at(frame)` rebuilds every actor's state at one frame from the record. The same
   frame always gives the same state.
3. 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:

```ts twoslash
// @filename: intro.scene.ts
import {
  createScene,
  LayoutActor,
} from "@motionactor/core";

export class Title extends LayoutActor {}

export const scene = createScene(function* (ctx) {
  const title = ctx.spawn(Title, {
    appearance: { opacity: 0 },
    layout: { x: 320, y: 160, width: 320, height: 160 },
  });

  yield* title.appearance.opacity.tween(1, 0.4);
  yield* ctx.wait(0.5);
  yield* title.layout.x.tween(640, 0.6);

  return { title };
});
// @filename: main.ts
// ---cut---
import { scene } from "./intro.scene";

using compiled = scene.build({ viewport: { width: 1280, height: 720 }, fps: 30 });
const title = compiled.at(36).scene.title;

title?.layout.x; // 480, halfway through the slide
title?.appearance.opacity; // 1
compiled.totalDuration; // 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](/docs/getting-started)**

An actor, a scene, a frame, then pixels.

**[How it works](/docs/mental-model)**

What runs at compile time, what runs per frame, and how they stay apart.

**[Scenes](/docs/runtime/scene-orchestration)**

Sequencing, parallel branches and staggers.

**[Reactivity](/docs/reactivity)**

signal, derived, channel and the rest of the state model.

**[Rendering](/docs/rendering)**

Renderers, backends, effects and hosts.

**[Examples](/docs/examples)**

Complete compositions, typechecked.
