Renderers
A renderer draws one actor class on one backend from the resolved state of one frame.
A renderer is a function from one resolved actor to what a backend draws. It declares the backend it draws for and the classes it handles, and it gets everything else from the frame.
import { class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
import { const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom } from "@motionactor/dom/react";
export class class BadgeBadge extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Badge.label: ActorFieldSignal<string>label = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal("New");
Badge.hue: ActorFieldSignal<number>hue = signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal(190);
}
export const const badge: BackendRendererDefinition<readonly [typeof Badge], "dom">badge = const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom.render: <typeof Badge>(blueprint: typeof Badge, render: ActorRendererForBlueprints<readonly [typeof Badge], "dom">, options?: ActorRendererRegistrationOptions) => BackendRendererDefinition<readonly [typeof Badge], "dom"> (+1 overload)render(class BadgeBadge, ({ actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 7 more ...;
label: string;
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor }) => (
<React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span React.HTMLAttributes<HTMLSpanElement>.style?: React.CSSProperties | undefinedstyle={{ StandardShorthandProperties<string | number, string & {}>.background?: Property.Background<string | number> | undefinedThis feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
**Syntax**: `<bg-layer>#? , <final-bg-layer>`
| Chrome | Firefox | Safari | Edge | IE |
| :----: | :-----: | :----: | :----: | :---: |
| **1** | **1** | **1** | **12** | **4** |background: `hsl(${actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 7 more ...;
label: string;
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor.signals: Readonly<Record<string, unknown>> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 7 more ...;
label: string;
}
signals.hue: numberhue} 80% 45%)` }}>
{actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 7 more ...;
label: string;
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor.signals: Readonly<Record<string, unknown>> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 7 more ...;
label: string;
}
signals.label: stringlabel}
</React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>
));
actor.signals is typed from the class, so hue is a number and label a string here. The
renderer never sees a signal object, only that signal’s value at this frame.
A renderer that reads actor draws again whenever anything about the actor changes. One that
reads signals instead draws again only when a key it read changes, so moving, scaling or
fading the badge leaves it alone:
import { class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
import { const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom } from "@motionactor/dom/react";
export class class BadgeBadge extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Badge.label: ActorFieldSignal<string>label = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal("New");
Badge.hue: ActorFieldSignal<number>hue = signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal(190);
}
export const const badge: BackendRendererDefinition<readonly [typeof Badge], "dom">badge = const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom.render: <typeof Badge>(blueprint: typeof Badge, render: ActorRendererForBlueprints<readonly [typeof Badge], "dom">, options?: ActorRendererRegistrationOptions) => BackendRendererDefinition<readonly [typeof Badge], "dom"> (+1 overload)render(class BadgeBadge, ({ signals: Readonly<Record<string, unknown>> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | `${number}%` | Readonly<...>;
};
... 7 more ...;
label: string;
}
The actor's signals. Reading them in place of `actor` makes the renderer
depend only on the keys it reads, wherever it reads them: a memoized
child or an effect handed `signals` reads through it too.signals }) => (
<React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span React.HTMLAttributes<HTMLSpanElement>.style?: React.CSSProperties | undefinedstyle={{ StandardShorthandProperties<string | number, string & {}>.background?: Property.Background<string | number> | undefinedThis feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
**Syntax**: `<bg-layer>#? , <final-bg-layer>`
| Chrome | Firefox | Safari | Edge | IE |
| :----: | :-----: | :----: | :----: | :---: |
| **1** | **1** | **1** | **12** | **4** |background: `hsl(${signals: Readonly<Record<string, unknown>> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | `${number}%` | Readonly<...>;
};
... 7 more ...;
label: string;
}
The actor's signals. Reading them in place of `actor` makes the renderer
depend only on the keys it reads, wherever it reads them: a memoized
child or an effect handed `signals` reads through it too.signals.hue: numberhue} 80% 45%)` }}>{signals: Readonly<Record<string, unknown>> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | `${number}%` | Readonly<...>;
};
... 7 more ...;
label: string;
}
The actor's signals. Reading them in place of `actor` makes the renderer
depend only on the keys it reads, wherever it reads them: a memoized
child or an effect handed `signals` reads through it too.signals.label: stringlabel}</React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>
));
The keys are recorded wherever they are read: destructured, in a memoized component or an effect
handed signals. A spread reads every key. The React binding tracks keys; Vue and Svelte update
the content on any change to the actor.
Matching
backend.render(classes, render, options?) takes one class or an array of them. A renderer matches an actor whose class is one of those classes or extends one, so a
renderer for Badge also draws class Pill extends Badge until something more specific
claims it.
When several renderers match, the first one wins. A render runtime orders them as listed in
renderers, then each backend’s own primitives, so an app renderer for a subclass always
comes before the backend’s renderer for its base. A renderer that returns null passes the
actor on to the next match.
import { function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
import { const createRenderRuntime: (input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>) => ReactRenderRuntimeThe floor's `createRenderRuntime` for React: every backend's host is a
React component and its stages wrap React nodes, so mixed backends (each
with its own host props) compose without naming a type.createRenderRuntime, function createRendererRegistry(...entries: RendererRegistryEntry[]): RendererRegistry (+1 overload)createRendererRegistry } from "@motionactor/renderers/react";
import { const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom } from "@motionactor/dom/react";
import { class BadgeBadge, const badge: BackendRendererDefinition<readonly [typeof Badge], "dom">badge } from "./badge";
class class PillPill extends class BadgeBadge {
Pill.muted: ActorFieldSignal<boolean>muted = signal<boolean>(initial: boolean): ActorFieldSignal<boolean> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal(false);
}
// Draws muted pills and passes the rest on to `badge`.
const const mutedPill: BackendRendererDefinition<readonly [typeof Pill], "dom">mutedPill = const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom.render: <typeof Pill>(blueprint: typeof Pill, render: ActorRendererForBlueprints<readonly [typeof Pill], "dom">, options?: ActorRendererRegistrationOptions) => BackendRendererDefinition<readonly [typeof Pill], "dom"> (+1 overload)render(class PillPill, ({ actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 8 more ...;
muted: boolean;
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor }) =>
actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 8 more ...;
muted: boolean;
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor.signals: Readonly<Record<string, unknown>> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 8 more ...;
muted: boolean;
}
signals.muted: booleanmuted ? (
<React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span React.HTMLAttributes<HTMLSpanElement>.className?: string | undefinedclassName="pill">{actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 8 more ...;
muted: boolean;
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor.signals: Readonly<Record<string, unknown>> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 8 more ...;
muted: boolean;
}
signals.label: stringlabel}</React.JSX.IntrinsicElements.span: React.DetailedHTMLProps<React.HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>span>
) : null,
);
export const const badges: RendererRegistrybadges = function createRendererRegistry(...entries: RendererRegistryEntry[]): RendererRegistry (+1 overload)createRendererRegistry(const mutedPill: BackendRendererDefinition<readonly [typeof Pill], "dom">mutedPill, const badge: BackendRendererDefinition<readonly [typeof Badge], "dom">badge);
export const const runtime: ReactRenderRuntimeruntime = function createRenderRuntime(input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>): ReactRenderRuntimeThe floor's `createRenderRuntime` for React: every backend's host is a
React component and its stages wrap React nodes, so mixed backends (each
with its own host props) compose without naming a type.createRenderRuntime({
backends: readonly AnyBackend<ReactBackendHost, React.ReactNode>[]backends: [const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom],
renderers?: readonly RendererRegistryEntry[] | undefinedFamily and project renderers, for any backend. Each routes to the backend it
names (`three.render(…)`); one naming a backend missing from
`backends` throws.renderers: [const badges: RendererRegistrybadges],
});
A registry is a reusable, ordered group of renderers. Registries nest, and
createRendererRegistry(base, { overrides: [...] }) puts the overrides ahead of everything in
base, the last override first. Pass a registry wherever a renderer list is expected.
An actor with renderers on several backends gets the one for the layer it is in. The runtime routes each renderer to the backend it names and throws when it lists a renderer for a backend it does not have.
The render context
| Field | What it holds |
|---|---|
actor |
The resolved actor: signals, localLayout, worldLayout, effects, compositing, channels |
state |
The whole FrameState, for reading other actors with state.findActor(Class) |
frame, fps |
The frame being drawn and the build’s frame rate |
compiled |
The compiled scene, when the host has one |
slots |
Hosted children by slot name, each as { actor, element }; a multiple slot is an array. Painting an element does not read its child; reading actor makes the renderer draw again when that child changes |
targets |
The actor’s declared effect targets, the same identities scene code applies effects to |
refs |
DOM: ref callbacks for those targets, so an effect can find the element, and the bindings refs.<child> and refs.self. Three: the bindings alone |
renderChild |
Draws a child actor through the same registry: a slot entry’s actor, or a copy derived from it. DOM: renderChild(actor, { flow }) places it in the element’s flow instead of at its layout box |
Slots
A slot is a place in an actor that other actors are spawned into. The renderer decides where each child’s element goes; the child’s own renderer draws the child.
import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal, function slot<const TAccepts extends SlotAccepts>(options: {
accepts: TAccepts;
multiple?: false;
}): OnePlaceField<TAccepts> (+1 overload)
Declare a place: a named position this actor's children are hosted in.
Written as a field; the field reads as a handle once the actor spawns.
```ts
screen = slot({ accepts: LayoutActor }); // this.screen.current
letters = slot({ accepts: Letter, multiple: true }); // this.letters.all()
```slot } from "@motionactor/core";
import { const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom } from "@motionactor/dom/react";
class class TagTag extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Tag.text: ActorFieldSignal<string>text = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal("");
}
class class CardCard extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Card.title: ActorFieldSignal<string>title = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal("Untitled");
Card.tags: ManyPlaceField<typeof Tag>tags = slot<typeof Tag>(options: {
accepts: typeof Tag;
multiple: true;
}): ManyPlaceField<typeof Tag> (+1 overload)
Declare a place: a named position this actor's children are hosted in.
Written as a field; the field reads as a handle once the actor spawns.
```ts
screen = slot({ accepts: LayoutActor }); // this.screen.current
letters = slot({ accepts: Letter, multiple: true }); // this.letters.all()
```slot({ accepts: typeof Tagaccepts: class TagTag, multiple: truemultiple: true });
}
export const const compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled = createScene<{
card: Card;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
card: Card;
}>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const card: Cardcard = ctx: SceneContextctx.CompileContext.spawn<Card>(definition: ActorClass<Card>, overrides?: {
id?: undefined;
localLayout?: undefined;
worldLayout?: undefined;
compositing?: {
blend?: SignalInputValue<"source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter"> | undefined;
isolate?: SignalInputValue<boolean> | undefined;
clip?: SignalInputValue<number | null> | undefined;
} | undefined;
... 19 more ...;
title?: SignalInputValue<...> | undefined;
} | undefined, options?: {
...;
} | undefined): Card (+1 overload)
spawn(class CardCard, { title?: SignalInputValue<string> | undefinedtitle: "Release notes" });
const card: Cardcard.Card.tags: ManyPlaceField<typeof Tag>tags.spawn<typeof Tag>(blueprint: typeof Tag, overrides?: {
id?: undefined;
localLayout?: undefined;
worldLayout?: undefined;
compositing?: {
blend?: SignalInputValue<"source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter"> | undefined;
isolate?: SignalInputValue<boolean> | undefined;
clip?: SignalInputValue<number | null> | undefined;
} | undefined;
... 19 more ...;
text?: SignalInputValue<...> | undefined;
} | undefined): Tag (+1 overload)
Host a child of `blueprint`, which must be an accepted class.spawn(class TagTag, { text?: SignalInputValue<string> | undefinedtext: "2.0" });
const card: Cardcard.Card.tags: ManyPlaceField<typeof Tag>tags.spawn<typeof Tag>(blueprint: typeof Tag, overrides?: {
id?: undefined;
localLayout?: undefined;
worldLayout?: undefined;
compositing?: {
blend?: SignalInputValue<"source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter"> | undefined;
isolate?: SignalInputValue<boolean> | undefined;
clip?: SignalInputValue<number | null> | undefined;
} | undefined;
... 19 more ...;
text?: SignalInputValue<...> | undefined;
} | undefined): Tag (+1 overload)
Host a child of `blueprint`, which must be an accepted class.spawn(class TagTag, { text?: SignalInputValue<string> | undefinedtext: "breaking" });
return { card: Cardcard };
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
export const const card: BackendRendererDefinition<readonly [typeof Card], "dom">card = const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom.render: <typeof Card>(blueprint: typeof Card, render: ActorRendererForBlueprints<readonly [typeof Card], "dom">, options?: ActorRendererRegistrationOptions) => BackendRendererDefinition<readonly [typeof Card], "dom"> (+1 overload)render(class CardCard, ({ actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 6 more ...;
title: string;
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor, slots: {
tags: {
actor: ResolvedActorOfInstance<Tag>;
element: RenderNode | null;
}[];
}
The actor's hosted children by field, each `{ actor, element }`. Reading
an `element` does not read its child; reading an `actor` makes the
renderer depend on it.slots }) => (
<React.JSX.IntrinsicElements.article: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>article>
<React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2>{actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 6 more ...;
title: string;
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor.signals: Readonly<Record<string, unknown>> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
};
... 6 more ...;
title: string;
}
signals.title: stringtitle}</React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2>
<React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>ul>
{slots: {
tags: {
actor: ResolvedActorOfInstance<Tag>;
element: RenderNode | null;
}[];
}
The actor's hosted children by field, each `{ actor, element }`. Reading
an `element` does not read its child; reading an `actor` makes the
renderer depend on it.slots.tags: {
actor: ResolvedActorOfInstance<Tag>;
element: RenderNode | null;
}[]
tags.Array<{ actor: ResolvedActorOfInstance<Tag>; element: RenderNode | null; }>.map<JSX.Element>(callbackfn: (value: {
actor: ResolvedActorOfInstance<Tag>;
element: RenderNode | null;
}, index: number, array: {
actor: ResolvedActorOfInstance<Tag>;
element: RenderNode | null;
}[]) => JSX.Element, thisArg?: any): JSX.Element[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.map(({ actor: ResolvedActorOfInstance<Tag>actor, element: React.ReactNodeelement }) => (
<React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>li React.Attributes.key?: React.Key | null | undefinedkey={actor: ResolvedActorOfInstance<Tag>actor.id: stringid}>{element: React.ReactNodeelement}</React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>li>
))}
</React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>ul>
</React.JSX.IntrinsicElements.article: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>article>
));
Bindings
A DOM renderer can paint a child(...) actor onto a node of its own markup instead of the
child’s element: refs.<child> binds the node to that child, and refs.self binds it to the
actor itself. The node keeps its place in the markup’s flow, and each frame the scope writes
the actor’s style onto it with no render of the view.
import { function child<TBlueprint extends ActorClass<object>, TOwner extends object = object>(blueprint: TBlueprint, ...[overrides]: ChildOverridesArg<TBlueprint, TOwner>): ChildField<TBlueprint, TOwner>child, function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
import { const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom } from "@motionactor/dom/react";
class class TitleTitle extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {}
class class CardCard extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Card.label: ActorFieldSignal<string>label = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal("Quarterly");
Card.title: ChildField<typeof Title, object>title = child<typeof Title, object>(blueprint: typeof Title, overrides?: ChildOverrides<typeof Title, object> | undefined): ChildField<typeof Title, object>child(class TitleTitle);
}
export const const compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled = createScene<{
card: Card;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
card: Card;
}>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const card: Cardcard = ctx: SceneContextctx.CompileContext.spawn<Card>(definition: ActorClass<Card>, overrides?: {
id?: undefined;
localLayout?: undefined;
worldLayout?: undefined;
compositing?: {
blend?: SignalInputValue<"source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter"> | undefined;
isolate?: SignalInputValue<boolean> | undefined;
clip?: SignalInputValue<number | null> | undefined;
} | undefined;
... 20 more ...;
label?: SignalInputValue<...> | undefined;
} | undefined, options?: {
...;
} | undefined): Card (+1 overload)
spawn(class CardCard);
yield* const card: Cardcard.Card.title: ChildField<typeof Title, object>title.LayoutActor<{}>.transform: {
scale: TypedFieldSignal<number, number>;
rotation: TypedFieldSignal<number, number>;
} & SignalGroupDefinition
transform.scale: TypedFieldSignal<number, number>scale.function tween(target: number, options: number | SignalTweenOptions<number>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(1.2, 0.4);
return { card: Cardcard };
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
export const const card: BackendRendererDefinition<readonly [typeof Card], "dom">card = const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom.render: <typeof Card>(blueprint: typeof Card, render: ActorRendererForBlueprints<readonly [typeof Card], "dom">, options?: ActorRendererRegistrationOptions) => BackendRendererDefinition<readonly [typeof Card], "dom"> (+1 overload)render(class CardCard, ({ signals: Readonly<Record<string, unknown>> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | `${number}%` | Readonly<...>;
};
... 7 more ...;
label: string;
}
The actor's signals. Reading them in place of `actor` makes the renderer
depend only on the keys it reads, wherever it reads them: a memoized
child or an effect handed `signals` reads through it too.signals, refs: TargetRefs<{}> & {
readonly title: BoundElementRef;
} & {
readonly self: BoundElementRef;
}
refs }) => (
<React.JSX.IntrinsicElements.article: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>article>
<React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1 React.RefAttributes<HTMLHeadingElement>.ref?: React.Ref<HTMLHeadingElement> | undefinedAllows getting a ref to the component instance.
Once the component unmounts, React will set `ref.current` to `null`
(or call the ref with `null` if you passed a callback ref).ref={refs: TargetRefs<{}> & {
readonly title: BoundElementRef;
} & {
readonly self: BoundElementRef;
}
refs.title: BoundElementReftitle}>{signals: Readonly<Record<string, unknown>> & {
compositing: {
blend: "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
x: LayoutValue;
y: LayoutValue;
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | `${number}%` | Readonly<...>;
};
... 7 more ...;
label: string;
}
The actor's signals. Reading them in place of `actor` makes the renderer
depend only on the keys it reads, wherever it reads them: a memoized
child or an effect handed `signals` reads through it too.signals.label: stringlabel}</React.JSX.IntrinsicElements.h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h1>
</React.JSX.IntrinsicElements.article: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>article>
));
Vue binds with the v-actor directive from @motionactor/dom/vue
(<h1 v-actor="refs.title">), and Svelte with an attachment (<h1 {@attach refs.title}>).
| While bound | The node gets |
|---|---|
| The actor is present | transform: scale() rotate() about the node’s own origin, opacity, blend mode, isolation and clip, and shape.radius as border-radius with overflow: hidden |
| It gets an inline effect | What the effect’s stage paints on an element’s wrapper, folded in: its filter, its transform after the actor’s own, its opacity multiplied, its clip-path. The effect’s box is the node’s own untransformed size, measured once and again when the node resizes |
| The actor is absent | visibility: hidden, so it keeps its space |
| The node unbinds | Markup’s own inline values for those properties back, as they were when it bound |
The markup places and sizes the node: the actor’s x, y, width and height are not written.
Whatever writes one of those properties into the node’s style (the view re-rendering, a
component’s own state, a script), the node takes the actor’s value back before paint. Its
measuredBox is published on demand, untransformed, against the element of the actor whose
view holds it, so a rotated node measures the box it was laid out in. An outlet rendered inside
a bound node is placed against that node, where flow put it, and follows it when flow moves it:
on every frame the scope presents, held or repeated, when the node resizes, and when fonts
load. Each node bound is its own instance: a view that binds a child and also renders its
outlet paints it twice.
Only HTML elements bind: refs.<child> and refs.self take HTMLElement | null. A binding
throws a BoundElementError at bind, in every build, naming the actor and the field, when the actor’s content is transformed
(contentTransform), when it gets an offscreen effect that does not clip or that no actor
element encloses, an effect whose stage paints only actor elements, when an effect needs an
overlay or a second clip, or for refs.self inside the actor’s own element, which already
paints it; a refused node registers nothing, so the view has nothing to release. An effect the
binding could not see at bind (an overlay a later frame starts, or any effect when the render
context carries no compiled scene) throws as the frame that brings it writes the node: the
binding lets go of the node first, back to its markup, and the frame’s other nodes still paint.
A target whose name a binding also uses is a type error on refs.
Offscreen effects
A bound node takes a glShader or canvasPaint effect when the effect’s target contract
clips (clips: true): its handler paints inside the target’s rect and leaves the rest of the
canvas as it was. The node registers in the capture of the actor element whose view binds it,
and that element mounts its canvas for the effect and runs it over the node’s rect. An effect
without target paints its whole canvas, the element around the node, so binding a node to an
actor that gets one throws a BoundElementError, in every build, before the node registers.
import { function child<TBlueprint extends ActorClass<object>, TOwner extends object = object>(blueprint: TBlueprint, ...[overrides]: ChildOverridesArg<TBlueprint, TOwner>): ChildField<TBlueprint, TOwner>child, function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene, function defineEffect<const TName extends string, const P extends EffectParamDeclarations, TKind extends AnyEffectKind, TTarget = unknown, const TTime extends boolean = false, TNative = unknown, const TApplies extends EffectApplication | undefined = undefined>(kind: TKind, spec: EffectSpec<TName, P, TTarget, TTime> & {
readonly params: NoPlaybackParams;
readonly applies?: TApplies;
} & EffectNativeSite<TNative> & EffectKindSpec<EffectKindTypesOf<TKind>, EffectRenderParams<P, TTime>, TTarget, TNative>): EffectDefinition<EffectKindBackendOf<TKind>, TName, P, TKind, TTarget, EffectKindRuntime<EffectKindTypesOf<TKind>, EffectRenderParams<P, TTime>, TTarget, TNative>, TTime, EffectDefinitionApplies<TApplies, TTarget, TKind>>
Define an effect: one value of one kind, on the kind's backend. The spec is
the core fields plus the kind's own, typed from `params`:
export const glow = defineEffect(domStyle, {
name: "glow",
params: { color: signal.of(colorType, color("#22d3ee")), spread: signal(18) },
style: ({ color, spread }) => ({ filter: `drop-shadow(0 0 ${spread}px ${color})` }),
});
declare module "@motionactor/core" { interface ActorEffects { "dom:glow": typeof glow } }
registerEffects([glow]);
card.fx.glow({ spread: 24 });defineEffect, const effectTargetContract: <TTarget>(contract: EffectTargetContractFor<TTarget>) => TypedEffectTargetContract<TTarget>A target contract that carries the target type its effect's render reads.
`dataKinds` and `geometry` are checked against that type.effectTargetContract, class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function registerEffects(definitions: readonly AnyEffectDefinition[]): voidInstall effects by wire id. Call it in the effect's own module, after its
`defineEffect(...)` (and beside its `ActorEffects` augmentation when it
applies to an actor), so importing the effect installs it.
Every effect registers. One that can apply to an actor is also on every
actor's `fx`; one that cannot (a layer application, or one needing target
data) stays off `fx` and applies with `ctx.apply` on a target. A later
registration at a wire id replaces the earlier one. An actor effect named
`for` throws: `fx.for` is taken.registerEffects, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
import { const glShader: EffectKind<"dom", "shader", GlShaderTypes, {
readonly kind: "offscreen";
readonly scope: "actor";
}, "actor">
The kind of effect that runs a WebGL pass over its actor's rasterized
subtree. The short form names uniforms by what they are:
export const halftone = defineEffect(glShader, {
name: "halftone",
params: { dotSize: signal(6) },
shader: { fs: HALFTONE_FS }, // vs defaults to the fullscreen quad
uniforms: ({ dotSize }, { progress }) => ({ dotSize: 1 + (dotSize - 1) * progress }),
});
`tex`, `resolution`, `progress` and `time` are bound by the kind when the
shader declares them; an effect's own value wins. A pass that draws itself
(several passes, extra textures, depth) supplies `paint(state, ctx)` in
place of `uniforms`.glShader, const targetRegionUniforms: (target: ResolvedCanvasEffectTarget | undefined) => {
readonly hasRegion: boolean;
readonly regionRect: readonly [number, number, number, number];
}
The `u_hasRegion` / `u_regionRect` pair a region-scoped shader reads: the
target's first rect or quad bounds in canvas UV, or the whole canvas for a
pass with no target. A pass whose target has no area never paints (the
pipeline skips it), so it never reads the whole canvas.targetRegionUniforms, type type CanvasTargetContract<TData, TGeometryKind extends CanvasTargetGeometryKind, TClips extends boolean = false> = {
readonly data: TData;
readonly geometry: TGeometryKind;
readonly clips: TClips;
}
CanvasTargetContract } from "@motionactor/dom";
import { const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom } from "@motionactor/dom/react";
const const FROST_FS: "#version 300 es\nprecision highp float;\nuniform sampler2D u_tex;\nuniform bool u_hasRegion;\nuniform vec4 u_regionRect;\nuniform float u_amount;\nin vec2 v_uv;\nout vec4 color;\nvoid main() {\n vec4 source = texture(u_tex, v_uv);\n vec2 local = (vec2(v_uv.x, 1.0 - v_uv.y) - u_regionRect.xy) / u_regionRect.zw;\n bool inside = all(greaterThanEqual(local, vec2(0.0))) && all(lessThanEqual(local, vec2(1.0)));\n color = u_hasRegion && !inside ? source : mix(source, vec4(1.0), u_amount);\n}"FROST_FS = `#version 300 es
precision highp float;
uniform sampler2D u_tex;
uniform bool u_hasRegion;
uniform vec4 u_regionRect;
uniform float u_amount;
in vec2 v_uv;
out vec4 color;
void main() {
vec4 source = texture(u_tex, v_uv);
vec2 local = (vec2(v_uv.x, 1.0 - v_uv.y) - u_regionRect.xy) / u_regionRect.zw;
bool inside = all(greaterThanEqual(local, vec2(0.0))) && all(lessThanEqual(local, vec2(1.0)));
color = u_hasRegion && !inside ? source : mix(source, vec4(1.0), u_amount);
}`;
/** Any rect it clips to: a target, or the node a view binds. */
type type ClippedRect = {
readonly data: unknown;
readonly geometry: "rect";
readonly clips: true;
}
Any rect it clips to: a target, or the node a view binds.ClippedRect = type CanvasTargetContract<TData, TGeometryKind extends CanvasTargetGeometryKind, TClips extends boolean = false> = {
readonly data: TData;
readonly geometry: TGeometryKind;
readonly clips: TClips;
}
CanvasTargetContract<unknown, "rect", true>;
export const const frost: EffectDefinition<"dom", "frost", {
readonly amount: ActorFieldSignal<number>;
}, EffectKind<"dom", "shader", GlShaderTypes, {
readonly kind: "offscreen";
readonly scope: "actor";
}, "actor">, ClippedRect, GlShaderRuntime<EffectParamValues<{
readonly amount: ActorFieldSignal<number>;
}>, ResolvedCanvasEffectTargetFor<unknown, "rect">>, false, "target">
frost = defineEffect<"frost", {
readonly amount: ActorFieldSignal<number>;
}, EffectKind<"dom", "shader", GlShaderTypes, {
readonly kind: "offscreen";
readonly scope: "actor";
}, "actor">, ClippedRect, false, unknown, undefined>(kind: EffectKind<"dom", "shader", GlShaderTypes, {
readonly kind: "offscreen";
readonly scope: "actor";
}, "actor">, spec: EffectSpec<"frost", {
readonly amount: ActorFieldSignal<number>;
}, ClippedRect, false> & ({
...;
} & GlShaderSpec<...>)): EffectDefinition<...>
Define an effect: one value of one kind, on the kind's backend. The spec is
the core fields plus the kind's own, typed from `params`:
export const glow = defineEffect(domStyle, {
name: "glow",
params: { color: signal.of(colorType, color("#22d3ee")), spread: signal(18) },
style: ({ color, spread }) => ({ filter: `drop-shadow(0 0 ${spread}px ${color})` }),
});
declare module "@motionactor/core" { interface ActorEffects { "dom:glow": typeof glow } }
registerEffects([glow]);
card.fx.glow({ spread: 24 });defineEffect(const glShader: EffectKind<"dom", "shader", GlShaderTypes, {
readonly kind: "offscreen";
readonly scope: "actor";
}, "actor">
The kind of effect that runs a WebGL pass over its actor's rasterized
subtree. The short form names uniforms by what they are:
export const halftone = defineEffect(glShader, {
name: "halftone",
params: { dotSize: signal(6) },
shader: { fs: HALFTONE_FS }, // vs defaults to the fullscreen quad
uniforms: ({ dotSize }, { progress }) => ({ dotSize: 1 + (dotSize - 1) * progress }),
});
`tex`, `resolution`, `progress` and `time` are bound by the kind when the
shader declares them; an effect's own value wins. A pass that draws itself
(several passes, extra textures, depth) supplies `paint(state, ctx)` in
place of `uniforms`.glShader, {
EffectSpec<"frost", { readonly amount: ActorFieldSignal<number>; }, ClippedRect, false>.name: "frost"Unique among the backend's effects. With the backend's name, the effect's wire identity.name: "frost",
params: {
readonly amount: ActorFieldSignal<number>;
} & NoPlaybackParams
Every param the kind's contribution reads, declared once as `signal(default)` or `signal.of(type, default)`.params: { amount: ActorFieldSignal<number>amount: signal<number>(initial: number): ActorFieldSignal<number> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal(0.4) },
EffectSpec<"frost", { readonly amount: ActorFieldSignal<number>; }, ClippedRect, false>.target?: TypedEffectTargetContract<ClippedRect> | undefinedWhat an explicit target must carry, and whether the effect clips to it: `effectTargetContract<T>({ dataKinds, geometry, clips })`.target: effectTargetContract<ClippedRect>(contract: EffectTargetContractFor<ClippedRect>): TypedEffectTargetContract<ClippedRect>A target contract that carries the target type its effect's render reads.
`dataKinds` and `geometry` are checked against that type.effectTargetContract<type ClippedRect = {
readonly data: unknown;
readonly geometry: "rect";
readonly clips: true;
}
Any rect it clips to: a target, or the node a view binds.ClippedRect>({ dataKinds: readonly string[]dataKinds: [], geometry: readonly "rect"[]geometry: ["rect"], clips: trueclips: true }),
shader: GlShaderProgram<EffectParamValues<{
readonly amount: ActorFieldSignal<number>;
}>>
shader: { GlShaderProgram<TParams>.fs: stringfs: const FROST_FS: "#version 300 es\nprecision highp float;\nuniform sampler2D u_tex;\nuniform bool u_hasRegion;\nuniform vec4 u_regionRect;\nuniform float u_amount;\nin vec2 v_uv;\nout vec4 color;\nvoid main() {\n vec4 source = texture(u_tex, v_uv);\n vec2 local = (vec2(v_uv.x, 1.0 - v_uv.y) - u_regionRect.xy) / u_regionRect.zw;\n bool inside = all(greaterThanEqual(local, vec2(0.0))) && all(lessThanEqual(local, vec2(1.0)));\n color = u_hasRegion && !inside ? source : mix(source, vec4(1.0), u_amount);\n}"FROST_FS },
uniforms: GlShaderUniforms<EffectParamValues<{
readonly amount: ActorFieldSignal<number>;
}>, ResolvedCanvasEffectTargetFor<unknown, "rect">>
uniforms: ({ amount: numberamount }, { target: ResolvedCanvasEffectTargetFor<unknown, "rect"> | undefinedThe explicit target, resolved to canvas geometry, when the effect was applied to one.target }) => ({ ...function targetRegionUniforms(target: ResolvedCanvasEffectTarget | undefined): {
readonly hasRegion: boolean;
readonly regionRect: readonly [number, number, number, number];
}
The `u_hasRegion` / `u_regionRect` pair a region-scoped shader reads: the
target's first rect or quad bounds in canvas UV, or the whole canvas for a
pass with no target. A pass whose target has no area never paints (the
pipeline skips it), so it never reads the whole canvas.targetRegionUniforms(target: ResolvedCanvasEffectTargetFor<unknown, "rect"> | undefinedThe explicit target, resolved to canvas geometry, when the effect was applied to one.target), amount: numberamount }),
});
declare module "@motionactor/core" {
interface ActorEffects {
"dom:frost": typeof const frost: EffectDefinition<"dom", "frost", {
readonly amount: ActorFieldSignal<number>;
}, EffectKind<"dom", "shader", GlShaderTypes, {
readonly kind: "offscreen";
readonly scope: "actor";
}, "actor">, ClippedRect, GlShaderRuntime<EffectParamValues<{
readonly amount: ActorFieldSignal<number>;
}>, ResolvedCanvasEffectTargetFor<unknown, "rect">>, false, "target">
frost;
}
}
function registerEffects(definitions: readonly AnyEffectDefinition[]): voidInstall effects by wire id. Call it in the effect's own module, after its
`defineEffect(...)` (and beside its `ActorEffects` augmentation when it
applies to an actor), so importing the effect installs it.
Every effect registers. One that can apply to an actor is also on every
actor's `fx`; one that cannot (a layer application, or one needing target
data) stays off `fx` and applies with `ctx.apply` on a target. A later
registration at a wire id replaces the earlier one. An actor effect named
`for` throws: `fx.for` is taken.registerEffects([const frost: EffectDefinition<"dom", "frost", {
readonly amount: ActorFieldSignal<number>;
}, EffectKind<"dom", "shader", GlShaderTypes, {
readonly kind: "offscreen";
readonly scope: "actor";
}, "actor">, ClippedRect, GlShaderRuntime<EffectParamValues<{
readonly amount: ActorFieldSignal<number>;
}>, ResolvedCanvasEffectTargetFor<unknown, "rect">>, false, "target">
frost]);
class class CardCard extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Card.panel: ChildField<typeof LayoutActor, object>panel = child<typeof LayoutActor, object>(blueprint: typeof LayoutActor, overrides?: ChildOverrides<typeof LayoutActor, object> | undefined): ChildField<typeof LayoutActor, object>child(class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor);
}
export const const compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled = createScene<{
card: Card;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
card: Card;
}>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const card: Cardcard = ctx: SceneContextctx.CompileContext.spawn<Card>(definition: ActorClass<Card>, overrides?: {
id?: undefined;
localLayout?: undefined;
worldLayout?: undefined;
compositing?: {
blend?: SignalInputValue<"source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "difference" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter"> | undefined;
isolate?: SignalInputValue<boolean> | undefined;
clip?: SignalInputValue<number | null> | undefined;
} | undefined;
... 19 more ...;
panel?: {
...;
} | undefined;
} | undefined, options?: {
...;
} | undefined): Card (+1 overload)
spawn(class CardCard);
const card: Cardcard.Card.panel: ChildField<typeof LayoutActor, object>panel.Actor<TEvents extends ActorEventMap = {}>.fx: ActorFxInstalled effects by name: `yield* this.fx.burnDissolve({ noiseScale: 7 }, { duration: 0.6 })`
plays one; `this.fx.glow({ spread: 24 })` keeps its handle. A name two
installed backends define is selected with `this.fx.for(dom).glow(...)`.
Applies only once spawned; the layer's backend is checked when bound.fx.frost: () => FxHandle<EffectDefinition<"dom", "frost", {
readonly amount: ActorFieldSignal<number>;
}, EffectKind<"dom", "shader", GlShaderTypes, {
readonly kind: "offscreen";
readonly scope: "actor";
}, "actor">, ClippedRect, GlShaderRuntime<EffectParamValues<{
readonly amount: ActorFieldSignal<number>;
}>, ResolvedCanvasEffectTargetFor<unknown, "rect">>, false, "target">, {}> (+3 overloads)
Apply at the cursor with every param at its default.frost();
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(1);
return { card: Cardcard };
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
export const const card: BackendRendererDefinition<readonly [typeof Card], "dom">card = const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom.render: <typeof Card>(blueprint: typeof Card, render: ActorRendererForBlueprints<readonly [typeof Card], "dom">, options?: ActorRendererRegistrationOptions) => BackendRendererDefinition<readonly [typeof Card], "dom"> (+1 overload)render(class CardCard, ({ refs: TargetRefs<{}> & {
readonly panel: BoundElementRef;
} & {
readonly self: BoundElementRef;
}
refs }) => (
<React.JSX.IntrinsicElements.article: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>article>
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.RefAttributes<HTMLDivElement>.ref?: React.Ref<HTMLDivElement> | undefinedAllows getting a ref to the component instance.
Once the component unmounts, React will set `ref.current` to `null`
(or call the ref with `null` if you passed a callback ref).ref={refs: TargetRefs<{}> & {
readonly panel: BoundElementRef;
} & {
readonly self: BoundElementRef;
}
refs.panel: BoundElementRefpanel}>Q3 revenue</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
</React.JSX.IntrinsicElements.article: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>article>
));
card.panel.fx offers every installed effect on any child: whether the view binds it or
renders it through its outlet is the view’s choice, so the check runs at bind. A child the view renders through its
outlet keeps its own element, which runs any of its effects in a canvas of its own. A page
binding has no actor element around it, so an offscreen effect on its actor throws at bind.
While a bound node has no area (display: none, zero size), its effect paints nothing; it
stays registered and paints again once the node shows.
Three bindings
A three renderer binds the same way, to objects of its own markup: refs.self binds an
Object3D to the actor, and refs.<child> binds one to each child(...). Any actor binds. At the
start of each advance of the canvas, before the composer and the render, the host writes each of
position, rotation and scale the actor’s blueprint declares as an x/y/z group onto the
object, so a frame that only moves an actor renders no component. An Object3DActor declares all
three; a LayoutActor declares none, so its object keeps the placement its markup gives it.
import { function child<TBlueprint extends ActorClass<object>, TOwner extends object = object>(blueprint: TBlueprint, ...[overrides]: ChildOverridesArg<TBlueprint, TOwner>): ChildField<TBlueprint, TOwner>child, function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene } from "@motionactor/core";
import { class Object3DActor<TEvents extends ActorEventMap = {}>Object3DActor, class Primitive3DPrimitive3D } from "@motionactor/three";
import { const three: Backend<ReactBackendHost, "three">three } from "@motionactor/three/react";
class class RigRig extends class Object3DActor<TEvents extends ActorEventMap = {}>Object3DActor {
Rig.lid: ChildField<typeof Primitive3D, object>lid = child<typeof Primitive3D, object>(blueprint: typeof Primitive3D, overrides?: ChildOverrides<typeof Primitive3D, object> | undefined): ChildField<typeof Primitive3D, object>child(class Primitive3DPrimitive3D);
}
export const const compiled: CompiledScene<{
rig: Rig;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled = createScene<{
rig: Rig;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
rig: Rig;
}>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it:
`scene.build({ viewport, fps, inputs })` runs the script as
`(ctx, inputs, viewport, layers)` and returns the compiled scene.
`ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn`
places it in a declared one. `ctx.stage` stages another scene on this
scene's clock, and a staged scene may stage again.createScene(function* (ctx: SceneContextctx) {
const const rig: Rigrig = ctx: SceneContextctx.CompileContext.spawn<Rig>(definition: ActorClass<Rig>, overrides?: {
id?: undefined;
cursor?: undefined;
cursorSeconds?: undefined;
sceneFps?: undefined;
onInit?: undefined;
onEnter?: undefined;
onExit?: undefined;
enter?: undefined;
exit?: undefined;
parent?: undefined;
position?: {
x?: SignalInputValue<number> | undefined;
y?: SignalInputValue<number> | undefined;
z?: SignalInputValue<number> | undefined;
} | undefined;
rotation?: {
x?: SignalInputValue<number> | undefined;
y?: SignalInputValue<number> | undefined;
z?: SignalInputValue<...> | undefined;
} | undefined;
... 4 more ...;
lid?: {
...;
} | undefined;
} | undefined, options?: {
...;
} | undefined): Rig (+1 overload)
spawn(class RigRig);
yield* const rig: Rigrig.Rig.lid: ChildField<typeof Primitive3D, object>lid.Object3DActor<{}>.rotateTo(rotation: Vector3, transition?: TweenTransition): Generator<number, void, unknown>rotateTo({ x: numberx: 0, y: numbery: var Math: MathAn intrinsic object that provides basic mathematics functionality and constants.Math.Math.PI: numberPi. This is the ratio of the circumference of a circle to its diameter.PI, z: numberz: 0 }, 1);
return { rig: Rigrig };
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
rig: Rig;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
export const const rig: BackendRendererDefinition<readonly [typeof Rig], "three">rig = const three: Backend<ReactBackendHost, "three">three.render: <typeof Rig>(blueprint: typeof Rig, render: ActorRendererForBlueprints<readonly [typeof Rig], "three">, options?: ActorRendererRegistrationOptions) => BackendRendererDefinition<readonly [typeof Rig], "three"> (+1 overload)render(class RigRig, ({ refs: BindingRefs<Rig, {
actor: ThreeBindable;
ref: BoundObjectRef;
}>
refs }) => (
<group: Mutable<Overwrite<Partial<Overwrite<Group<Object3DEventMap>, MathProps<Group<Object3DEventMap>> & ReactProps<Group<Object3DEventMap>> & Partial<EventHandlers>>>, Omit<InstanceProps<Group<Object3DEventMap>, typeof Group>, "object">>>group ref?: ((instance: Group<Object3DEventMap> | null) => void | (() => VoidOrUndefinedOnly) | DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | ... 4 more ... | undefinedref={refs: BindingRefs<Rig, {
actor: ThreeBindable;
ref: BoundObjectRef;
}>
refs.self: BoundObjectRefself}>
<mesh: Mutable<Overwrite<Partial<Overwrite<Mesh<BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>, Material<MaterialEventMap> | Material<MaterialEventMap>[], Object3DEventMap>, MathProps<Mesh<BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>, Material<MaterialEventMap> | Material<MaterialEventMap>[], Object3DEventMap>> & ReactProps<...> & Partial<...>>>, Omit<...>>>mesh ref?: ((instance: Mesh<BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>, Material<MaterialEventMap> | Material<MaterialEventMap>[], Object3DEventMap> | null) => void | (() => VoidOrUndefinedOnly) | DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | ... 4 more ... | undefinedref={refs: BindingRefs<Rig, {
actor: ThreeBindable;
ref: BoundObjectRef;
}>
refs.lid: BoundObjectReflid}>
<boxGeometry: Mutable<Overwrite<Partial<Overwrite<BoxGeometry, MathProps<BoxGeometry> & ReactProps<BoxGeometry>>>, Omit<InstanceProps<BoxGeometry, typeof BoxGeometry>, "object">>>boxGeometry />
<meshStandardMaterial: Mutable<Overwrite<Partial<Overwrite<MeshStandardMaterial, MathProps<MeshStandardMaterial> & ReactProps<MeshStandardMaterial>>>, Omit<InstanceProps<MeshStandardMaterial, typeof MeshStandardMaterial>, "object">>>meshStandardMaterial />
</mesh: Mutable<Overwrite<Partial<Overwrite<Mesh<BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>, Material<MaterialEventMap> | Material<MaterialEventMap>[], Object3DEventMap>, MathProps<Mesh<BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>, Material<MaterialEventMap> | Material<MaterialEventMap>[], Object3DEventMap>> & ReactProps<...> & Partial<...>>>, Omit<...>>>mesh>
</group: Mutable<Overwrite<Partial<Overwrite<Group<Object3DEventMap>, MathProps<Group<Object3DEventMap>> & ReactProps<Group<Object3DEventMap>> & Partial<EventHandlers>>>, Omit<InstanceProps<Group<Object3DEventMap>, typeof Group>, "object">>>group>
));
| While bound | The object gets |
|---|---|
| The actor is present | Each of position, rotation and scale its blueprint declares |
| The actor is absent | visible = false |
| The object unbinds | Its visibility, and each group it took, as it had them when it bound |
A bound object is the actor’s effect target, whatever the actor: rig.lid.fx.<name>() selects
the meshes under the object bound to lid. A LayoutActor drawn by a three view binds its root
the same way, and takes effects without any placement:
import { class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor } from "@motionactor/core";
import { const three: Backend<ReactBackendHost, "three">three } from "@motionactor/three/react";
class class GalleryGallery extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {}
export const const gallery: BackendRendererDefinition<readonly [typeof Gallery], "three">gallery = const three: Backend<ReactBackendHost, "three">three.render: <typeof Gallery>(blueprint: typeof Gallery, render: ActorRendererForBlueprints<readonly [typeof Gallery], "three">, options?: ActorRendererRegistrationOptions) => BackendRendererDefinition<readonly [typeof Gallery], "three"> (+1 overload)render(class GalleryGallery, ({ refs: BindingRefs<Gallery, {
actor: ThreeBindable;
ref: BoundObjectRef;
}>
refs }) => (
<group: Mutable<Overwrite<Partial<Overwrite<Group<Object3DEventMap>, MathProps<Group<Object3DEventMap>> & ReactProps<Group<Object3DEventMap>> & Partial<EventHandlers>>>, Omit<InstanceProps<Group<Object3DEventMap>, typeof Group>, "object">>>group ref?: ((instance: Group<Object3DEventMap> | null) => void | (() => VoidOrUndefinedOnly) | DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | ... 4 more ... | undefinedref={refs: BindingRefs<Gallery, {
actor: ThreeBindable;
ref: BoundObjectRef;
}>
refs.self: BoundObjectRefself} position?: Vector3 | [x: number, y: number, z: number] | Readonly<number | Vector3 | [x: number, y: number, z: number] | undefined>Object's local position.position={[0, 0, -4]}>
<mesh: Mutable<Overwrite<Partial<Overwrite<Mesh<BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>, Material<MaterialEventMap> | Material<MaterialEventMap>[], Object3DEventMap>, MathProps<Mesh<BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>, Material<MaterialEventMap> | Material<MaterialEventMap>[], Object3DEventMap>> & ReactProps<...> & Partial<...>>>, Omit<...>>>mesh>
<planeGeometry: Mutable<Overwrite<Partial<Overwrite<PlaneGeometry, MathProps<PlaneGeometry> & ReactProps<PlaneGeometry>>>, Omit<InstanceProps<PlaneGeometry, typeof PlaneGeometry>, "object">>>planeGeometry />
<meshStandardMaterial: Mutable<Overwrite<Partial<Overwrite<MeshStandardMaterial, MathProps<MeshStandardMaterial> & ReactProps<MeshStandardMaterial>>>, Omit<InstanceProps<MeshStandardMaterial, typeof MeshStandardMaterial>, "object">>>meshStandardMaterial />
</mesh: Mutable<Overwrite<Partial<Overwrite<Mesh<BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>, Material<MaterialEventMap> | Material<MaterialEventMap>[], Object3DEventMap>, MathProps<Mesh<BufferGeometry<NormalBufferAttributes, BufferGeometryEventMap>, Material<MaterialEventMap> | Material<MaterialEventMap>[], Object3DEventMap>> & ReactProps<...> & Partial<...>>>, Omit<...>>>mesh>
</group: Mutable<Overwrite<Partial<Overwrite<Group<Object3DEventMap>, MathProps<Group<Object3DEventMap>> & ReactProps<Group<Object3DEventMap>> & Partial<EventHandlers>>>, Omit<InstanceProps<Group<Object3DEventMap>, typeof Group>, "object">>>group>
));
Only Object3Ds bind; anything else throws a BoundObjectError. Primitive3D’s own renderer
binds its mesh through refs.self.
Page bindings
Markup outside every actor’s view binds an actor too. actor.<tag> from
@motionactor/dom/react renders that element bound to the actor a live handle names: a handle
from the scene’s exports, compiled.scene. Render it among SceneRenderer’s children, which
render in the scope of the scene’s DOM layer. Below, nothing draws the card: the heading is
the page’s own, and each frame the DOM layer writes the title’s style onto it with no render
of the page.
import { const createRenderRuntime: (input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>) => ReactRenderRuntimeThe floor's `createRenderRuntime` for React: every backend's host is a
React component and its stages wrap React nodes, so mixed backends (each
with its own host props) compose without naming a type.createRenderRuntime } from "@motionactor/renderers/react";
import { const actor: ActorTagsPage markup bound to an actor: `<actor.h1 of={compiled.scene.card.title}
className="text-6xl">Launch</actor.h1>` renders a heading the scene's DOM
layer writes each frame the title moves, with no render of the page. Render
it among `SceneRenderer`'s children, which render in the scope of the
scene's DOM layer; the actor must paint in that layer. It renders the
frame's style inline, on the server too; while the actor is absent, it
keeps its place in flow, hidden. It owns nothing: the actor's element is
its view's, and a view binds a node of its own markup with `refs`.actor, const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom } from "@motionactor/dom/react";
import type { const scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
card: Card;
}>
scene } from "./page-heading.scene";
export const const runtime: ReactRenderRuntimeruntime = function createRenderRuntime(input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>): ReactRenderRuntimeThe floor's `createRenderRuntime` for React: every backend's host is a
React component and its stages wrap React nodes, so mixed backends (each
with its own host props) compose without naming a type.createRenderRuntime({
backends: readonly AnyBackend<ReactBackendHost, React.ReactNode>[]backends: [const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom],
renderers?: readonly RendererRegistryEntry[] | undefinedFamily and project renderers, for any backend. Each routes to the backend it
names (`three.render(…)`); one naming a backend missing from
`backends` throws.renderers: [],
});
// The page's markup for a build, rendered as SceneRenderer's children.
// The scene's DOM layer writes the heading each frame.
export const const page: (compiled: ReturnType<typeof scene.build>) => JSX.Elementpage = (compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled: type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : anyObtain the return type of a function typeReturnType<typeof const scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
card: Card;
}>
scene.SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build>) => (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="grid h-[720px] place-items-center">
<const actor: ActorTagsPage markup bound to an actor: `<actor.h1 of={compiled.scene.card.title}
className="text-6xl">Launch</actor.h1>` renders a heading the scene's DOM
layer writes each frame the title moves, with no render of the page. Render
it among `SceneRenderer`'s children, which render in the scope of the
scene's DOM layer; the actor must paint in that layer. It renders the
frame's style inline, on the server too; while the actor is absent, it
keeps its place in flow, hidden. It owns nothing: the actor's element is
its view's, and a view binds a node of its own markup with `refs`.actor.h1: React.FC<ActorTagProps<"h1">>h1
of: PageBindableA live handle of the scene's exports: `compiled.scene.card.title`.of={compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ ...; }, CompiledLayersOf<...>>.scene: {
card: Card;
}
What the scene's generator returned: its exported actors as the handles
it authored with (`compiled.scene.card.title`). After the build they only
name their actors: a write or lifecycle call through one throws, and a
frame's values are read from `at(frame).scene`. Page markup binds one
(`<actor.h1 of={compiled.scene.card.title}>`).scene.card: Cardcard.Card.title: ChildField<typeof Title, object>title}
className?: string | undefinedclassName="text-9xl font-medium text-[var(--stage-ink)]"
>
Launch
</const actor: ActorTagsPage markup bound to an actor: `<actor.h1 of={compiled.scene.card.title}
className="text-6xl">Launch</actor.h1>` renders a heading the scene's DOM
layer writes each frame the title moves, with no render of the page. Render
it among `SceneRenderer`'s children, which render in the scope of the
scene's DOM layer; the actor must paint in that layer. It renders the
frame's style inline, on the server too; while the actor is absent, it
keeps its place in flow, hidden. It owns nothing: the actor's element is
its view's, and a view binds a node of its own markup with `refs`.actor.h1: React.FC<ActorTagProps<"h1">>h1>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
);
// @filename: stage.tsx
import { const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
compiled: CompiledScene<TSceneExports>;
frame: number;
width: number;
height: number;
sceneMeta?: unknown;
style?: React.CSSProperties;
children?: React.ReactNode;
runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers (`SceneLayers`) on a stage of `width` by
`height`. Each layer is painted by the host its backend registers on the
runtime; the default layer by the runtime's default backend. `children` are
page markup: they render in the scope of the scene's DOM layer, where
`<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor
(`SceneLayers`).SceneRenderer } from "@motionactor/renderers/react";
import type { const scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
card: Card;
}>
scene } from "./page-heading.scene";
import { const page: (compiled: ReturnType<typeof scene.build>) => JSX.Elementpage, const runtime: ReactRenderRuntimeruntime } from "./page-heading.render";
type type Build = CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
Build = type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : anyObtain the return type of a function typeReturnType<typeof const scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
card: Card;
}>
scene.SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, { ...; }>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
build>;
export const const Stage: ({ compiled, frame }: {
compiled: Build;
frame: number;
}) => JSX.Element
Stage = ({ compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled, frame: numberframe }: { compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled: type Build = CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
Build; frame: numberframe: number }) => (
<const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
compiled: CompiledScene<TSceneExports>;
frame: number;
width: number;
height: number;
sceneMeta?: unknown;
style?: React.CSSProperties;
children?: React.ReactNode;
runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers (`SceneLayers`) on a stage of `width` by
`height`. Each layer is painted by the host its backend registers on the
runtime; the default layer by the runtime's default backend. `children` are
page markup: they render in the scope of the scene's DOM layer, where
`<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor
(`SceneLayers`).SceneRenderer compiled: CompiledScene<{
card: Card;
}, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>
compiled={compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled} frame: numberframe={frame: numberframe} width: numberwidth={1280} height: numberheight={720} runtime?: ReactRenderRuntime | undefinedruntime={const runtime: ReactRenderRuntimeruntime}>
{function page(compiled: ReturnType<typeof scene.build>): JSX.Elementpage(compiled: CompiledScene<{
card: Card;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled)}
</const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
compiled: CompiledScene<TSceneExports>;
frame: number;
width: number;
height: number;
sceneMeta?: unknown;
style?: React.CSSProperties;
children?: React.ReactNode;
runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers (`SceneLayers`) on a stage of `width` by
`height`. Each layer is painted by the host its backend registers on the
runtime; the default layer by the runtime's default backend. `children` are
page markup: they render in the scope of the scene's DOM layer, where
`<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor
(`SceneLayers`).SceneRenderer>
);
Vue renders <actor.h1 :of="compiled.scene.card.title" class="text-6xl"> in a
<script setup> template, with actor from @motionactor/dom/vue, in SceneRenderer’s
default slot. Svelte renders <actor.h1 of={compiled.scene.card.title} class="text-6xl">, with
actor from @motionactor/dom/svelte, among its children.
The node is written exactly as a refs binding writes one (the table above), and keeps its
place in the page’s flow. The server render and the first render carry the frame’s style
inline, after the element’s own style, with the actor’s inline effects folded in as the node
paints them: a domStyle effect that fades and blurs renders its opacity and blur(…) filter
on the server too. An effect whose style reads the node’s box has no box before the node
mounts and is measured, so the server render carries its box-less form and the node takes the
measured one on its first frame. Before the actor spawns, the element’s own values, hidden.
of takes a live handle of a LayoutActor: a frame’s resolved actor, or an actor with no
layout, is a type error, and each tag takes its own element’s props.
| The scene | Its page markup renders in |
|---|---|
| The runtime’s default backend is DOM | The default layer, mounted for the markup even when none of its actors paint |
| Another backend paints the default layer | The first declared layer DOM paints |
| No layer is DOM | Beneath every layer: an actor.<tag> there throws, naming the actor |
The markup renders at its layer’s origin, beneath the layer’s actors and under its camera;
layer order and clipping stay as declared. To render part of it in a later DOM layer, wrap that
part in InLayer with the build’s handle on the layer, compiled.layers.<key>; markup with no
address stays in the first DOM layer.
import { const createRenderRuntime: (input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>) => ReactRenderRuntimeThe floor's `createRenderRuntime` for React: every backend's host is a
React component and its stages wrap React nodes, so mixed backends (each
with its own host props) compose without naming a type.createRenderRuntime, const InLayer: ({ layer, children }: Readonly<{
layer: LayerAddress;
children?: React.ReactNode;
}>) => JSX.Element | null
Places its markup in the DOM layer `layer` addresses, a layer of the build
the enclosing `SceneRenderer` renders: `<InLayer layer={compiled.layers.ui}>`,
or a composition's `compiled.layersOf(scene).ui`. Anywhere in the page
markup outside an actor's view, through any component, its markup renders
in that layer's host (`PageContent`); markup with no address renders in the
scene's first DOM layer. Nothing renders while the layer is not on stage.
A changed address moves the markup; one of another build throws as it
renders. Moved markup renders under the addressed layer's host, so a React
context provided between `SceneRenderer` and the `InLayer` does not reach it.InLayer, const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
compiled: CompiledScene<TSceneExports>;
frame: number;
width: number;
height: number;
sceneMeta?: unknown;
style?: React.CSSProperties;
children?: React.ReactNode;
runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers (`SceneLayers`) on a stage of `width` by
`height`. Each layer is painted by the host its backend registers on the
runtime; the default layer by the runtime's default backend. `children` are
page markup: they render in the scope of the scene's DOM layer, where
`<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor
(`SceneLayers`).SceneRenderer } from "@motionactor/renderers/react";
import { const actor: ActorTagsPage markup bound to an actor: `<actor.h1 of={compiled.scene.card.title}
className="text-6xl">Launch</actor.h1>` renders a heading the scene's DOM
layer writes each frame the title moves, with no render of the page. Render
it among `SceneRenderer`'s children, which render in the scope of the
scene's DOM layer; the actor must paint in that layer. It renders the
frame's style inline, on the server too; while the actor is absent, it
keeps its place in flow, hidden. It owns nothing: the actor's element is
its view's, and a view binds a node of its own markup with `refs`.actor, const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom } from "@motionactor/dom/react";
import { const compiled: CompiledScene<{
lead: Label;
badge: Label;
}, CompiledLayersOf<{
readonly page: SceneLayer<BackendRef<"dom">>;
readonly overlay: SceneLayer<BackendRef<"dom">>;
}>>
compiled } from "./badge.scene";
const const runtime: ReactRenderRuntimeruntime = function createRenderRuntime(input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>): ReactRenderRuntimeThe floor's `createRenderRuntime` for React: every backend's host is a
React component and its stages wrap React nodes, so mixed backends (each
with its own host props) compose without naming a type.createRenderRuntime({ backends: readonly AnyBackend<ReactBackendHost, React.ReactNode>[]backends: [const dom: StagedBackend<React.FC<ReactBackendHostProps>, "dom", React.ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom], renderers?: readonly RendererRegistryEntry[] | undefinedFamily and project renderers, for any backend. Each routes to the backend it
names (`three.render(…)`); one naming a backend missing from
`backends` throws.renderers: [] });
export const const Stage: ({ frame }: {
frame: number;
}) => JSX.Element
Stage = ({ frame: numberframe }: { frame: numberframe: number }) => (
<const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
compiled: CompiledScene<TSceneExports>;
frame: number;
width: number;
height: number;
sceneMeta?: unknown;
style?: React.CSSProperties;
children?: React.ReactNode;
runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers (`SceneLayers`) on a stage of `width` by
`height`. Each layer is painted by the host its backend registers on the
runtime; the default layer by the runtime's default backend. `children` are
page markup: they render in the scope of the scene's DOM layer, where
`<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor
(`SceneLayers`).SceneRenderer compiled: CompiledScene<{
lead: Label;
badge: Label;
}, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>
compiled={const compiled: CompiledScene<{
lead: Label;
badge: Label;
}, CompiledLayersOf<{
readonly page: SceneLayer<BackendRef<"dom">>;
readonly overlay: SceneLayer<BackendRef<"dom">>;
}>>
compiled} frame: numberframe={frame: numberframe} width: numberwidth={1280} height: numberheight={720} runtime?: ReactRenderRuntime | undefinedruntime={const runtime: ReactRenderRuntimeruntime}>
<const actor: ActorTagsPage markup bound to an actor: `<actor.h1 of={compiled.scene.card.title}
className="text-6xl">Launch</actor.h1>` renders a heading the scene's DOM
layer writes each frame the title moves, with no render of the page. Render
it among `SceneRenderer`'s children, which render in the scope of the
scene's DOM layer; the actor must paint in that layer. It renders the
frame's style inline, on the server too; while the actor is absent, it
keeps its place in flow, hidden. It owns nothing: the actor's element is
its view's, and a view binds a node of its own markup with `refs`.actor.h2: React.FC<ActorTagProps<"h2">>h2 of: PageBindableA live handle of the scene's exports: `compiled.scene.card.title`.of={const compiled: CompiledScene<{
lead: Label;
badge: Label;
}, CompiledLayersOf<{
readonly page: SceneLayer<BackendRef<"dom">>;
readonly overlay: SceneLayer<BackendRef<"dom">>;
}>>
compiled.CompiledScene<{ lead: Label; badge: Label; }, CompiledLayersOf<{ readonly page: SceneLayer<BackendRef<"dom">>; readonly overlay: SceneLayer<BackendRef<"dom">>; }>>.scene: {
lead: Label;
badge: Label;
}
What the scene's generator returned: its exported actors as the handles
it authored with (`compiled.scene.card.title`). After the build they only
name their actors: a write or lifecycle call through one throws, and a
frame's values are read from `at(frame).scene`. Page markup binds one
(`<actor.h1 of={compiled.scene.card.title}>`).scene.lead: Labellead}>Launch</const actor: ActorTagsPage markup bound to an actor: `<actor.h1 of={compiled.scene.card.title}
className="text-6xl">Launch</actor.h1>` renders a heading the scene's DOM
layer writes each frame the title moves, with no render of the page. Render
it among `SceneRenderer`'s children, which render in the scope of the
scene's DOM layer; the actor must paint in that layer. It renders the
frame's style inline, on the server too; while the actor is absent, it
keeps its place in flow, hidden. It owns nothing: the actor's element is
its view's, and a view binds a node of its own markup with `refs`.actor.h2: React.FC<ActorTagProps<"h2">>h2>
<const InLayer: ({ layer, children }: Readonly<{
layer: LayerAddress;
children?: React.ReactNode;
}>) => JSX.Element | null
Places its markup in the DOM layer `layer` addresses, a layer of the build
the enclosing `SceneRenderer` renders: `<InLayer layer={compiled.layers.ui}>`,
or a composition's `compiled.layersOf(scene).ui`. Anywhere in the page
markup outside an actor's view, through any component, its markup renders
in that layer's host (`PageContent`); markup with no address renders in the
scene's first DOM layer. Nothing renders while the layer is not on stage.
A changed address moves the markup; one of another build throws as it
renders. Moved markup renders under the addressed layer's host, so a React
context provided between `SceneRenderer` and the `InLayer` does not reach it.InLayer layer: LayerAddresslayer={const compiled: CompiledScene<{
lead: Label;
badge: Label;
}, CompiledLayersOf<{
readonly page: SceneLayer<BackendRef<"dom">>;
readonly overlay: SceneLayer<BackendRef<"dom">>;
}>>
compiled.CompiledScene<{ lead: Label; badge: Label; }, CompiledLayersOf<{ readonly page: SceneLayer<BackendRef<"dom">>; readonly overlay: SceneLayer<BackendRef<"dom">>; }>>.layers: CompiledLayersOf<{
readonly page: SceneLayer<BackendRef<"dom">>;
readonly overlay: SceneLayer<BackendRef<"dom">>;
}>
Its scene's declared layers by key: what page markup addresses
(`<InLayer layer={compiled.layers.ui}>`). Empty for a scene that declares
none. The layers of a scene it stages are `compiled.layersOf(scene)`.layers.overlay: CompiledLayer<BackendRef<"dom">>overlay}>
<const actor: ActorTagsPage markup bound to an actor: `<actor.h1 of={compiled.scene.card.title}
className="text-6xl">Launch</actor.h1>` renders a heading the scene's DOM
layer writes each frame the title moves, with no render of the page. Render
it among `SceneRenderer`'s children, which render in the scope of the
scene's DOM layer; the actor must paint in that layer. It renders the
frame's style inline, on the server too; while the actor is absent, it
keeps its place in flow, hidden. It owns nothing: the actor's element is
its view's, and a view binds a node of its own markup with `refs`.actor.p: React.FC<ActorTagProps<"p">>p of: PageBindableA live handle of the scene's exports: `compiled.scene.card.title`.of={const compiled: CompiledScene<{
lead: Label;
badge: Label;
}, CompiledLayersOf<{
readonly page: SceneLayer<BackendRef<"dom">>;
readonly overlay: SceneLayer<BackendRef<"dom">>;
}>>
compiled.CompiledScene<{ lead: Label; badge: Label; }, CompiledLayersOf<{ readonly page: SceneLayer<BackendRef<"dom">>; readonly overlay: SceneLayer<BackendRef<"dom">>; }>>.scene: {
lead: Label;
badge: Label;
}
What the scene's generator returned: its exported actors as the handles
it authored with (`compiled.scene.card.title`). After the build they only
name their actors: a write or lifecycle call through one throws, and a
frame's values are read from `at(frame).scene`. Page markup binds one
(`<actor.h1 of={compiled.scene.card.title}>`).scene.badge: Labelbadge}>New</const actor: ActorTagsPage markup bound to an actor: `<actor.h1 of={compiled.scene.card.title}
className="text-6xl">Launch</actor.h1>` renders a heading the scene's DOM
layer writes each frame the title moves, with no render of the page. Render
it among `SceneRenderer`'s children, which render in the scope of the
scene's DOM layer; the actor must paint in that layer. It renders the
frame's style inline, on the server too; while the actor is absent, it
keeps its place in flow, hidden. It owns nothing: the actor's element is
its view's, and a view binds a node of its own markup with `refs`.actor.p: React.FC<ActorTagProps<"p">>p>
</const InLayer: ({ layer, children }: Readonly<{
layer: LayerAddress;
children?: React.ReactNode;
}>) => JSX.Element | null
Places its markup in the DOM layer `layer` addresses, a layer of the build
the enclosing `SceneRenderer` renders: `<InLayer layer={compiled.layers.ui}>`,
or a composition's `compiled.layersOf(scene).ui`. Anywhere in the page
markup outside an actor's view, through any component, its markup renders
in that layer's host (`PageContent`); markup with no address renders in the
scene's first DOM layer. Nothing renders while the layer is not on stage.
A changed address moves the markup; one of another build throws as it
renders. Moved markup renders under the addressed layer's host, so a React
context provided between `SceneRenderer` and the `InLayer` does not reach it.InLayer>
</const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
compiled: CompiledScene<TSceneExports>;
frame: number;
width: number;
height: number;
sceneMeta?: unknown;
style?: React.CSSProperties;
children?: React.ReactNode;
runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers (`SceneLayers`) on a stage of `width` by
`height`. Each layer is painted by the host its backend registers on the
runtime; the default layer by the runtime's default backend. `children` are
page markup: they render in the scope of the scene's DOM layer, where
`<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor
(`SceneLayers`).SceneRenderer>
);
Vue writes <InLayer :layer="compiled.layers.overlay"> in the default slot, and Svelte
<InLayer layer={compiled.layers.overlay}>, each from its @motionactor/renderers entry.
compiled.layers holds one handle per declared layer, typed by its backend: a layer another
backend paints, a key string, or a layer of another build does not pass. An InLayer may sit
anywhere in the page markup outside an actor’s view, inside your own components too. Its markup
renders in the layer it addresses, and the server renders it there too. Changing its layer
moves the markup. In React, context provided between SceneRenderer and an InLayer does not
reach the moved markup, so provide it above SceneRenderer. An InLayer inside another layer’s
moved markup, or inside an actor’s view, throws.
compiled.layers holds the built scene’s own layers. A staged scene’s, at any depth, come from
compiled.layersOf(scene), keyed by the definition staged. Two scenes that declare the same key
stay apart, and each handle is typed by its own scene’s backend. The markup renders only while
that scene is on stage.
import { const createRenderRuntime: (input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>) => ReactRenderRuntimeThe floor's `createRenderRuntime` for React: every backend's host is a
React component and its stages wrap React nodes, so mixed backends (each
with its own host props) compose without naming a type.createRenderRuntime, const InLayer: ({ layer, children }: Readonly<{
layer: LayerAddress;
children?: React.ReactNode;
}>) => JSX.Element | null
Places its markup in the DOM layer `layer` addresses, a layer of the build
the enclosing `SceneRenderer` renders: `<InLayer layer={compiled.layers.ui}>`,
or a composition's `compiled.layersOf(scene).ui`. Anywhere in the page
markup outside an actor's view, through any component, its markup renders
in that layer's host (`PageContent`); markup with no address renders in the
scene's first DOM layer. Nothing renders while the layer is not on stage.
A changed address moves the markup; one of another build throws as it
renders. Moved markup renders under the addressed layer's host, so a React
context provided between `SceneRenderer` and the `InLayer` does not reach it.InLayer, const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
compiled: CompiledScene<TSceneExports>;
frame: number;
width: number;
height: number;
sceneMeta?: unknown;
style?: React.CSSProperties;
children?: React.ReactNode;
runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers (`SceneLayers`) on a stage of `width` by
`height`. Each layer is painted by the host its backend registers on the
runtime; the default layer by the runtime's default backend. `children` are
page markup: they render in the scope of the scene's DOM layer, where
`<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor
(`SceneLayers`).SceneRenderer } from "@motionactor/renderers/react";
import { const actor: ActorTagsPage markup bound to an actor: `<actor.h1 of={compiled.scene.card.title}
className="text-6xl">Launch</actor.h1>` renders a heading the scene's DOM
layer writes each frame the title moves, with no render of the page. Render
it among `SceneRenderer`'s children, which render in the scope of the
scene's DOM layer; the actor must paint in that layer. It renders the
frame's style inline, on the server too; while the actor is absent, it
keeps its place in flow, hidden. It owns nothing: the actor's element is
its view's, and a view binds a node of its own markup with `refs`.actor, const dom: StagedBackend<FC<ReactBackendHostProps>, "dom", ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom } from "@motionactor/dom/react";
import { const compiled: CompiledScene<{
badge: Label;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled, const outro: SceneDefinition<Readonly<Record<never, SceneInput>>, {
readonly overlay: SceneLayer<BackendRef<"dom">>;
}, {
badge: Label;
}>
outro } from "./staged.scene";
const const runtime: ReactRenderRuntimeruntime = function createRenderRuntime(input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>): ReactRenderRuntimeThe floor's `createRenderRuntime` for React: every backend's host is a
React component and its stages wrap React nodes, so mixed backends (each
with its own host props) compose without naming a type.createRenderRuntime({ backends: readonly AnyBackend<ReactBackendHost, ReactNode>[]backends: [const dom: StagedBackend<FC<ReactBackendHostProps>, "dom", ReactNode>The React DOM backend, and the `dom` binding code names: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom] })`.dom], renderers?: readonly RendererRegistryEntry[] | undefinedFamily and project renderers, for any backend. Each routes to the backend it
names (`three.render(…)`); one naming a backend missing from
`backends` throws.renderers: [] });
const const Overlay: () => JSX.ElementOverlay = () => (
<const InLayer: ({ layer, children }: Readonly<{
layer: LayerAddress;
children?: React.ReactNode;
}>) => JSX.Element | null
Places its markup in the DOM layer `layer` addresses, a layer of the build
the enclosing `SceneRenderer` renders: `<InLayer layer={compiled.layers.ui}>`,
or a composition's `compiled.layersOf(scene).ui`. Anywhere in the page
markup outside an actor's view, through any component, its markup renders
in that layer's host (`PageContent`); markup with no address renders in the
scene's first DOM layer. Nothing renders while the layer is not on stage.
A changed address moves the markup; one of another build throws as it
renders. Moved markup renders under the addressed layer's host, so a React
context provided between `SceneRenderer` and the `InLayer` does not reach it.InLayer layer: LayerAddresslayer={const compiled: CompiledScene<{
badge: Label;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ badge: Label; }, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>.layersOf<{
readonly overlay: SceneLayer<BackendRef<"dom">>;
}>(scene: {
readonly __motionactorKind: "scene-definition";
readonly layers: {
readonly overlay: SceneLayer<BackendRef<"dom">>;
};
}): CompiledLayersOf<{
readonly overlay: SceneLayer<BackendRef<"dom">>;
}>
The build's handles on the layers `scene` declares, keyed like its
declaration and typed by each backend:
`<InLayer layer={compiled.layersOf(outro).overlay}>`. Handles belong to the
definition: a scene staged twice keeps its handles, each in force at its
own frames. `layersOf(built)` is `compiled.layers`. Throws for a scene the
build did not stage.layersOf(const outro: SceneDefinition<Readonly<Record<never, SceneInput>>, {
readonly overlay: SceneLayer<BackendRef<"dom">>;
}, {
badge: Label;
}>
outro).overlay: CompiledLayer<BackendRef<"dom">>overlay}>
<const actor: ActorTagsPage markup bound to an actor: `<actor.h1 of={compiled.scene.card.title}
className="text-6xl">Launch</actor.h1>` renders a heading the scene's DOM
layer writes each frame the title moves, with no render of the page. Render
it among `SceneRenderer`'s children, which render in the scope of the
scene's DOM layer; the actor must paint in that layer. It renders the
frame's style inline, on the server too; while the actor is absent, it
keeps its place in flow, hidden. It owns nothing: the actor's element is
its view's, and a view binds a node of its own markup with `refs`.actor.p: FC<ActorTagProps<"p">>p of: PageBindableA live handle of the scene's exports: `compiled.scene.card.title`.of={const compiled: CompiledScene<{
badge: Label;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled.CompiledScene<{ badge: Label; }, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>.scene: {
badge: Label;
}
What the scene's generator returned: its exported actors as the handles
it authored with (`compiled.scene.card.title`). After the build they only
name their actors: a write or lifecycle call through one throws, and a
frame's values are read from `at(frame).scene`. Page markup binds one
(`<actor.h1 of={compiled.scene.card.title}>`).scene.badge: Labelbadge}>New</const actor: ActorTagsPage markup bound to an actor: `<actor.h1 of={compiled.scene.card.title}
className="text-6xl">Launch</actor.h1>` renders a heading the scene's DOM
layer writes each frame the title moves, with no render of the page. Render
it among `SceneRenderer`'s children, which render in the scope of the
scene's DOM layer; the actor must paint in that layer. It renders the
frame's style inline, on the server too; while the actor is absent, it
keeps its place in flow, hidden. It owns nothing: the actor's element is
its view's, and a view binds a node of its own markup with `refs`.actor.p: FC<ActorTagProps<"p">>p>
</const InLayer: ({ layer, children }: Readonly<{
layer: LayerAddress;
children?: React.ReactNode;
}>) => JSX.Element | null
Places its markup in the DOM layer `layer` addresses, a layer of the build
the enclosing `SceneRenderer` renders: `<InLayer layer={compiled.layers.ui}>`,
or a composition's `compiled.layersOf(scene).ui`. Anywhere in the page
markup outside an actor's view, through any component, its markup renders
in that layer's host (`PageContent`); markup with no address renders in the
scene's first DOM layer. Nothing renders while the layer is not on stage.
A changed address moves the markup; one of another build throws as it
renders. Moved markup renders under the addressed layer's host, so a React
context provided between `SceneRenderer` and the `InLayer` does not reach it.InLayer>
);
export const const Stage: ({ frame }: {
frame: number;
}) => JSX.Element
Stage = ({ frame: numberframe }: { frame: numberframe: number }) => (
<const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
compiled: CompiledScene<TSceneExports>;
frame: number;
width: number;
height: number;
sceneMeta?: unknown;
style?: React.CSSProperties;
children?: React.ReactNode;
runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers (`SceneLayers`) on a stage of `width` by
`height`. Each layer is painted by the host its backend registers on the
runtime; the default layer by the runtime's default backend. `children` are
page markup: they render in the scope of the scene's DOM layer, where
`<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor
(`SceneLayers`).SceneRenderer compiled: CompiledScene<{
badge: Label;
}, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>
compiled={const compiled: CompiledScene<{
badge: Label;
}, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>
compiled} frame: numberframe={frame: numberframe} width: numberwidth={1280} height: numberheight={720} runtime?: ReactRenderRuntime | undefinedruntime={const runtime: ReactRenderRuntimeruntime}>
<JSX.IntrinsicElements.section: DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>section>
<const Overlay: () => JSX.ElementOverlay />
</JSX.IntrinsicElements.section: DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>section>
</const SceneRenderer: <TSceneExports>({ compiled, frame, width, height, sceneMeta, style, children, runtime, }: {
compiled: CompiledScene<TSceneExports>;
frame: number;
width: number;
height: number;
sceneMeta?: unknown;
style?: React.CSSProperties;
children?: React.ReactNode;
runtime?: ReactRenderRuntime;
}) => JSX.Element
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers (`SceneLayers`) on a stage of `width` by
`height`. Each layer is painted by the host its backend registers on the
runtime; the default layer by the runtime's default backend. `children` are
page markup: they render in the scope of the scene's DOM layer, where
`<actor.h1 of={compiled.scene.card.title}>` binds a node of it to an actor
(`SceneLayers`).SceneRenderer>
);
An actor.<tag> is checked as it binds, before its actor spawns and on the server too. It
throws, naming the actor, outside any SceneRenderer; for a handle of another build, even one
whose id this build shares; and for an actor that paints in a layer other than the markup’s.
For that last case, wrap the markup in its layer’s InLayer, spawn the actor through the
markup’s layer, or bind it from a view. Whether the actor is present only shows or hides the
node. It owns nothing: the actor’s element, if it has one, is still its view’s.
A render method on the class
A DOM actor can carry its own render method instead of a separate renderer. The DOM backend
paints it with no registration, and a registered renderer for the class still takes
precedence. Inside it, this.<signal>() reads the signal’s value at the frame being drawn.
import { class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor, function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal } from "@motionactor/core";
export class class NoteNote extends class LayoutActor<TEvents extends ActorEventMap = {}>LayoutActor {
Note.text: ActorFieldSignal<string>text = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)A timeline value on an actor, and an input to every spawn of it.
Without an initial value the signal is required: `ctx.spawn` must supply it.signal("");
Note.render(this: {
text(): string;
}): JSX.Element
render(this: {
text(): string;
}
this: { function text(): stringtext(): string }) {
return <React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p React.HTMLAttributes<HTMLParagraphElement>.className?: string | undefinedclassName="note">{this.function text(): stringtext()}</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>;
}
}
A class with a render method counts as a DOM renderer when a layer decides what it can
paint, so spawn it into a DOM layer.