Skip to content
MotionActor
Esc
↑↓navigate↵open⌘Jpreview

Writing a backend

Build a backend for another drawing surface, with a host, renderers and an effect kind of its own.

A backend is three values: a ref that scenes import, a host component that mounts a drawing surface, and the effect kinds the surface can paint. This section builds one for SVG. Each actor becomes a <g>, SVG filters become effects, and the stock scene machinery does the rest: timing, slots, seeking, capture.

Every circle above is an SVG element drawn by the host on this page. Each one carries an effect of the same kind: a blur, a halo and a wobble.

Page
Effect kinds The SVG filter kind, its stage, and the three effects in the figure
Holding a frame Drawing that waits for a texture or a font, and surfaces with their own React tree

The ref

The ref names the backend. It is plain data with no React in it, so a scene file, a renderer package or an effect can name the backend without pulling the host into its module graph:

import { const defineBackend: <const TName extends string>(name: TName) => BackendRef<TName>defineBackend } from "@motionactor/core";

export const const svg: BackendRef<"svg">svg = defineBackend<"svg">(name: "svg"): BackendRef<"svg">defineBackend("svg");

The host

The host is a React component the scene renderer mounts once per layer that uses the backend. It gets the frame’s actors and draws them:

import { function useCallback<T extends Function>(callback: T, deps: React.DependencyList): T
`useCallback` will return a memoized version of the callback that only changes if one of the `inputs` has changed.
@version16.8.0@see{@link https://react.dev/reference/react/useCallback}
useCallback
, type type ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<ReactNode> | React.ReactPortal | Promise<AwaitedReactNode> | null | undefined
Represents all of the things React can render. Where {@link ReactElement } only represents JSX, `ReactNode` represents everything that can be rendered.
@see{@link https://react-typescript-cheatsheet.netlify.app/docs/react-types/reactnode/ React TypeScript Cheatsheet}@example```tsx // Typing children type Props = { children: ReactNode } const Component = ({ children }: Props) => <div>{children}</div> <Component>hello</Component> ```@example```tsx // Typing a custom element type Props = { customElement: ReactNode } const Component = ({ customElement }: Props) => <div>{customElement}</div> <Component customElement={<div>hello</div>} /> ```
ReactNode
} from "react";
import { const assertSubtreeCompositingSupported: (roots: readonly ResolvedActor[], actors: ReadonlyMap<string, ResolvedActor>, backend: BackendRef) => void
Assert a whole subtree, roots first, then hosted children in paint order. A backend that cannot map compositing groups has to check the descendants too — the group is usually the host, not the actor the host was handed.
assertSubtreeCompositingSupported
,
type ResolvedActor, } from "@motionactor/core"; import { function createBackend<TName extends string, THost>(ref: BackendRef<TName>, def: StagelessBackendDefinition<THost>): Backend<THost, TName> (+1 overload)
A backend: its host, its primitives and the stages it paints effects with. It is also its ref (the name and brand are copied from `ref`), and `render` defines a renderer on it. Effects are not listed here: each effect's module registers it (`registerEffects`), and a runtime resolves its backends'. export const dom = createBackend(domRef, { host: DomHost, renderers: [groupRenderer], stages: [{ kinds: [domStyle], stage: domEffectStage }], }) const dial = dom.render(Dial, ({ actor }) => …) Its own `renderers` are built with `defineActorRenderer(ref, …)`: the backend does not exist yet when they are. A backend with no stages wraps no node, so it joins any binding's runtime; one with stages wraps their node type `TNode`.
createBackend
,
type
type BackendHostProps<TNode = never> = [TNode] extends [never] ? Readonly<{
    actors: readonly ResolvedActor[];
    state: FrameState;
    frame: number;
    compiled: CompiledScene<unknown>;
    registry: RendererRegistry;
    width: number;
    height: number;
    layer: string | null;
}> & Readonly<{
    effectStages: readonly EffectStageInfo[];
}> : StagedHostProps<TNode>
What a host receives. With a node type its stages wrap that node; without one (`BackendHostProps`) the host wraps nothing, so it reads its stages as `EffectStageInfo` and fits any binding's runtime.
BackendHostProps
,
const groupEffectsByStage: <TStage extends GroupedStage>(effects: readonly ActiveEffect[], stages: readonly TStage[]) => readonly EffectStageRun<TStage>[]
Hand each of an actor's applied effects to the stage that paints it, and return every stage in wrap order (innermost first) with its effects — a stage with none is still returned, so it can host actor-owned passes or emit its identity wrapper. An effect goes to the stage that paints its definition's kind. A binding carries its definition when a scene applied it; one that lost it (a wire id that crossed IPC) is resolved from the effect registry (`registerEffects`) and rebound to that definition, so render code always reads the definition off the binding. An effect no stage paints is dropped here: its backend's host applies it, or nothing can.
groupEffectsByStage
,
const ActorRendererOutlet: React.MemoExoticComponent<(props: Readonly<{
    id: string;
    actor?: ResolvedActor;
    compiled: CompiledScene<unknown>;
    registry: RendererRegistry;
    bindings?: ViewBindings;
    wrapActor?: (actor: ResolvedActor, content: React.ReactNode) => React.ReactNode;
}>) => JSX.Element | null>
A registered renderer's output for one actor, and the recursion into its hosted children, for hosts that paint into a canvas. It selects its actor by id from the frame scope's store, memoized on its props, and renders only when what the renderer read changed: its actor (all of it, or the keys of `signals` it read), the frame, or the hosted children it read through `slots`.
ActorRendererOutlet
,
const FrameScope: ({ state, children, }: Readonly<{
    state: FrameState;
    children?: React.ReactNode;
}>) => JSX.Element
The frame a scene host paints, for everything below it: actor elements select their actor from its store by id, and the publications below run once per frame at it. `SceneLayers` renders one; a host that paints actors at a frame without it renders its own. The scope stages the frame into its store as it renders, so every element rendering after it reads that frame. Its insertion effect, which React runs after its subtree's DOM mutations and before any layout effect of the commit, writes the element styles the frame moved, in every pass, and then settles the bound nodes against the patched DOM, whatever the frame. Its layout effect, which runs after its descendants', wakes the elements the frame changed that did not render; they render in a second, woken pass, and so does the scope, whose layout effect then runs after theirs. The frame is stamped once, in that insertion effect; a frame that will be woken defers its publications, and the frame's last commit finishes them against the patched DOM. Its contexts hold only the store and the publications, which never change: a frame passed through context would make React search every held element's subtree for its readers each frame.
FrameScope
,
} from "@motionactor/renderers/react"; import { const svg: BackendRef<"svg">svg as const svgRef: BackendRef<"svg">svgRef } from "./svg"; import {
const svgFilter: EffectKind<"svg", "filter", SvgFilterTypes, {
    readonly kind: "inline";
}, "actor">
svgFilter
, const svgFilterStage: EffectStage<ReactNode>
Each effect gets its own filter, wrapped around the ones before it.
svgFilterStage
} from "./svg-filter";
function function SvgHost(props: BackendHostProps<ReactNode>): JSX.ElementSvgHost(props: StagedHostProps<ReactNode>props:
type BackendHostProps<TNode = never> = [TNode] extends [never] ? Readonly<{
    actors: readonly ResolvedActor[];
    state: FrameState;
    frame: number;
    compiled: CompiledScene<unknown>;
    registry: RendererRegistry;
    width: number;
    height: number;
    layer: string | null;
}> & Readonly<{
    effectStages: readonly EffectStageInfo[];
}> : StagedHostProps<TNode>
What a host receives. With a node type its stages wrap that node; without one (`BackendHostProps`) the host wraps nothing, so it reads its stages as `EffectStageInfo` and fits any binding's runtime.
BackendHostProps
<type ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<ReactNode> | React.ReactPortal | Promise<AwaitedReactNode> | null | undefined
Represents all of the things React can render. Where {@link ReactElement } only represents JSX, `ReactNode` represents everything that can be rendered.
@see{@link https://react-typescript-cheatsheet.netlify.app/docs/react-types/reactnode/ React TypeScript Cheatsheet}@example```tsx // Typing children type Props = { children: ReactNode } const Component = ({ children }: Props) => <div>{children}</div> <Component>hello</Component> ```@example```tsx // Typing a custom element type Props = { customElement: ReactNode } const Component = ({ customElement }: Props) => <div>{customElement}</div> <Component customElement={<div>hello</div>} /> ```
ReactNode
>) {
const { const actors: readonly ResolvedActor[]actors, const state: FrameState<unknown>state, const compiled: CompiledScene<unknown, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>compiled, const registry: RendererRegistryregistry, const width: numberwidth, const height: numberheight,
const effectStages: readonly Readonly<{
    stage: EffectStage<ReactNode>;
    raster: EffectRaster;
    kinds: readonly AnyEffectKind[];
    node?: NodeEffectPainter;
}>[]
The backend's effect stages, already in wrap order (innermost first).
effectStages
} = props: StagedHostProps<ReactNode>props;
function assertSubtreeCompositingSupported(roots: readonly ResolvedActor[], actors: ReadonlyMap<string, ResolvedActor>, backend: BackendRef): void
Assert a whole subtree, roots first, then hosted children in paint order. A backend that cannot map compositing groups has to check the descendants too — the group is usually the host, not the actor the host was handed.
assertSubtreeCompositingSupported
(const actors: readonly ResolvedActor[]actors, const state: FrameState<unknown>state.FrameState<unknown>.actors: ReadonlyMap<string, ResolvedActor>actors, const svgRef: BackendRef<"svg">svgRef);
// One function while the stages hold, so outlets skip actors that did not change. const const place: (actor: ResolvedActor, content: ReactNode) => JSX.Elementplace = useCallback<(actor: ResolvedActor, content: ReactNode) => JSX.Element>(callback: (actor: ResolvedActor, content: ReactNode) => JSX.Element, deps: React.DependencyList): (actor: ResolvedActor, content: ReactNode) => JSX.Element
`useCallback` will return a memoized version of the callback that only changes if one of the `inputs` has changed.
@version16.8.0@see{@link https://react.dev/reference/react/useCallback}
useCallback
((actor: ResolvedActoractor: ResolvedActor, content: ReactNodecontent: type ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<ReactNode> | React.ReactPortal | Promise<AwaitedReactNode> | null | undefined
Represents all of the things React can render. Where {@link ReactElement } only represents JSX, `ReactNode` represents everything that can be rendered.
@see{@link https://react-typescript-cheatsheet.netlify.app/docs/react-types/reactnode/ React TypeScript Cheatsheet}@example```tsx // Typing children type Props = { children: ReactNode } const Component = ({ children }: Props) => <div>{children}</div> <Component>hello</Component> ```@example```tsx // Typing a custom element type Props = { customElement: ReactNode } const Component = ({ customElement }: Props) => <div>{customElement}</div> <Component customElement={<div>hello</div>} /> ```
ReactNode
) => {
const { const x: numberx, const y: numbery, const width: numberwidth, const height: numberheight } = actor: ResolvedActoractor.ResolvedActor.localLayout: Readonly<Rect>localLayout; let let node: ReactNodenode = content: ReactNodecontent; for (const
const run: Readonly<{
    stage: Readonly<{
        stage: EffectStage<ReactNode>;
        raster: EffectRaster;
        kinds: readonly AnyEffectKind[];
        node?: NodeEffectPainter;
    }>;
    effects: readonly ActiveEffect[];
}>
run
of
groupEffectsByStage<Readonly<{
    stage: EffectStage<ReactNode>;
    raster: EffectRaster;
    kinds: readonly AnyEffectKind[];
    node?: NodeEffectPainter;
}>>(effects: readonly ActiveEffect[], stages: readonly Readonly<{
    stage: EffectStage<ReactNode>;
    raster: EffectRaster;
    kinds: readonly AnyEffectKind[];
    node?: NodeEffectPainter;
}>[]): readonly Readonly<...>[]
Hand each of an actor's applied effects to the stage that paints it, and return every stage in wrap order (innermost first) with its effects — a stage with none is still returned, so it can host actor-owned passes or emit its identity wrapper. An effect goes to the stage that paints its definition's kind. A binding carries its definition when a scene applied it; one that lost it (a wire id that crossed IPC) is resolved from the effect registry (`registerEffects`) and rebound to that definition, so render code always reads the definition off the binding. An effect no stage paints is dropped here: its backend's host applies it, or nothing can.
groupEffectsByStage
(actor: ResolvedActoractor.ResolvedActor.effects: readonly ActiveEffect[]effects,
const effectStages: readonly Readonly<{
    stage: EffectStage<ReactNode>;
    raster: EffectRaster;
    kinds: readonly AnyEffectKind[];
    node?: NodeEffectPainter;
}>[]
The backend's effect stages, already in wrap order (innermost first).
effectStages
)) {
const
const context: {
    actor: ResolvedActor;
    effects: readonly ActiveEffect[];
    box: {
        width: number;
        height: number;
    };
}
context
= { actor: ResolvedActoractor, effects: readonly ActiveEffect[]effects:
const run: Readonly<{
    stage: Readonly<{
        stage: EffectStage<ReactNode>;
        raster: EffectRaster;
        kinds: readonly AnyEffectKind[];
        node?: NodeEffectPainter;
    }>;
    effects: readonly ActiveEffect[];
}>
run
.effects: readonly ActiveEffect[]effects,
box: {
    width: number;
    height: number;
}
box
: { width: numberwidth, height: numberheight } };
let node: ReactNodenode =
const run: Readonly<{
    stage: Readonly<{
        stage: EffectStage<ReactNode>;
        raster: EffectRaster;
        kinds: readonly AnyEffectKind[];
        node?: NodeEffectPainter;
    }>;
    effects: readonly ActiveEffect[];
}>
run
.
stage: Readonly<{
    stage: EffectStage<ReactNode>;
    raster: EffectRaster;
    kinds: readonly AnyEffectKind[];
    node?: NodeEffectPainter;
}>
stage
.stage: EffectStage<ReactNode>stage.EffectStage<React.ReactNode>.wrap: (context: EffectStageContext, node: ReactNode) => ReactNode
A property, not a method: its node input is checked contravariantly.
wrap
(
const context: {
    actor: ResolvedActor;
    effects: readonly ActiveEffect[];
    box: {
        width: number;
        height: number;
    };
}
context
, let node: ReactNodenode);
} return ( <React.JSX.IntrinsicElements.g: React.SVGProps<SVGGElement>g data-actor: stringdata-actor={actor: ResolvedActoractor.ResolvedActor.id: stringid} React.SVGAttributes<SVGGElement>.transform?: string | undefinedtransform={`translate(${const x: numberx} ${const y: numbery})`} React.SVGAttributes<SVGGElement>.opacity?: string | number | undefinedopacity={actor: ResolvedActoractor.ResolvedActor.compositing: ResolvedCompositing
Resolved from the `compositing` group and `appearance.opacity`; identity when the actor declares neither, so a backend never reconstructs it from `signals`.
compositing
.ResolvedCompositing.opacity: number
Folded from `appearance.opacity` — the group's own alpha.
opacity
}
> {let node: ReactNodenode} </React.JSX.IntrinsicElements.g: React.SVGProps<SVGGElement>g> ); }, [
const effectStages: readonly Readonly<{
    stage: EffectStage<ReactNode>;
    raster: EffectRaster;
    kinds: readonly AnyEffectKind[];
    node?: NodeEffectPainter;
}>[]
The backend's effect stages, already in wrap order (innermost first).
effectStages
]);
return ( <React.JSX.IntrinsicElements.svg: React.SVGProps<SVGSVGElement>svg React.SVGAttributes<SVGSVGElement>.width?: string | number | undefinedwidth={const width: numberwidth} React.SVGAttributes<SVGSVGElement>.height?: string | number | undefinedheight={const height: numberheight} React.SVGAttributes<SVGSVGElement>.viewBox?: string | undefinedviewBox={`0 0 ${const width: numberwidth} ${const height: numberheight}`}> <
const FrameScope: ({ state, children, }: Readonly<{
    state: FrameState;
    children?: React.ReactNode;
}>) => JSX.Element
The frame a scene host paints, for everything below it: actor elements select their actor from its store by id, and the publications below run once per frame at it. `SceneLayers` renders one; a host that paints actors at a frame without it renders its own. The scope stages the frame into its store as it renders, so every element rendering after it reads that frame. Its insertion effect, which React runs after its subtree's DOM mutations and before any layout effect of the commit, writes the element styles the frame moved, in every pass, and then settles the bound nodes against the patched DOM, whatever the frame. Its layout effect, which runs after its descendants', wakes the elements the frame changed that did not render; they render in a second, woken pass, and so does the scope, whose layout effect then runs after theirs. The frame is stamped once, in that insertion effect; a frame that will be woken defers its publications, and the frame's last commit finishes them against the patched DOM. Its contexts hold only the store and the publications, which never change: a frame passed through context would make React search every held element's subtree for its readers each frame.
FrameScope
state: FrameState<unknown>state={const state: FrameState<unknown>state}>
{const actors: readonly ResolvedActor[]actors.ReadonlyArray<ResolvedActor>.map<JSX.Element>(callbackfn: (value: ResolvedActor, index: number, array: readonly ResolvedActor[]) => 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.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((actor: ResolvedActoractor) => (
<
const ActorRendererOutlet: React.MemoExoticComponent<(props: Readonly<{
    id: string;
    actor?: ResolvedActor;
    compiled: CompiledScene<unknown>;
    registry: RendererRegistry;
    bindings?: ViewBindings;
    wrapActor?: (actor: ResolvedActor, content: React.ReactNode) => React.ReactNode;
}>) => JSX.Element | null>
A registered renderer's output for one actor, and the recursion into its hosted children, for hosts that paint into a canvas. It selects its actor by id from the frame scope's store, memoized on its props, and renders only when what the renderer read changed: its actor (all of it, or the keys of `signals` it read), the frame, or the hosted children it read through `slots`.
ActorRendererOutlet
React.Attributes.key?: React.Key | null | undefinedkey={actor: ResolvedActoractor.ResolvedActor.id: stringid} id: string
The actor this outlet paints, read at the scope's frame.
id
={actor: ResolvedActoractor.ResolvedActor.id: stringid}
compiled: CompiledScene<unknown, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>compiled={const compiled: CompiledScene<unknown, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>compiled} registry: RendererRegistryregistry={const registry: RendererRegistryregistry} wrapActor?: ((actor: ResolvedActor, content: React.ReactNode) => React.ReactNode) | undefined
Backend-owned boundary, applied to every actor including hosted children.
wrapActor
={const place: (actor: ResolvedActor, content: ReactNode) => JSX.Elementplace}
/> ))} </
const FrameScope: ({ state, children, }: Readonly<{
    state: FrameState;
    children?: React.ReactNode;
}>) => JSX.Element
The frame a scene host paints, for everything below it: actor elements select their actor from its store by id, and the publications below run once per frame at it. `SceneLayers` renders one; a host that paints actors at a frame without it renders its own. The scope stages the frame into its store as it renders, so every element rendering after it reads that frame. Its insertion effect, which React runs after its subtree's DOM mutations and before any layout effect of the commit, writes the element styles the frame moved, in every pass, and then settles the bound nodes against the patched DOM, whatever the frame. Its layout effect, which runs after its descendants', wakes the elements the frame changed that did not render; they render in a second, woken pass, and so does the scope, whose layout effect then runs after theirs. The frame is stamped once, in that insertion effect; a frame that will be woken defers its publications, and the frame's last commit finishes them against the patched DOM. Its contexts hold only the store and the publications, which never change: a frame passed through context would make React search every held element's subtree for its readers each frame.
FrameScope
>
</React.JSX.IntrinsicElements.svg: React.SVGProps<SVGSVGElement>svg> ); } // The backend under the ref's name, so renderer files import one `svg`. export const const svg: StagedBackend<(props: BackendHostProps<ReactNode>) => JSX.Element, "svg", ReactNode>svg = createBackend<"svg", (props: BackendHostProps<ReactNode>) => JSX.Element, ReactNode>(ref: BackendRef<"svg">, def: BackendDefinition<(props: BackendHostProps<ReactNode>) => JSX.Element, ReactNode>): StagedBackend<(props: BackendHostProps<ReactNode>) => JSX.Element, "svg", ReactNode> (+1 overload)
A backend: its host, its primitives and the stages it paints effects with. It is also its ref (the name and brand are copied from `ref`), and `render` defines a renderer on it. Effects are not listed here: each effect's module registers it (`registerEffects`), and a runtime resolves its backends'. export const dom = createBackend(domRef, { host: DomHost, renderers: [groupRenderer], stages: [{ kinds: [domStyle], stage: domEffectStage }], }) const dial = dom.render(Dial, ({ actor }) => …) Its own `renderers` are built with `defineActorRenderer(ref, …)`: the backend does not exist yet when they are. A backend with no stages wraps no node, so it joins any binding's runtime; one with stages wraps their node type `TNode`.
createBackend
(const svgRef: BackendRef<"svg">svgRef, {
host: (props: BackendHostProps<ReactNode>) => JSX.Element
Whatever this backend's binding mounts for a frame — a React component for the DOM binding, something else for another framework. The runtime carries it through without knowing what it is; the renderer that mounts it says what it expects when it creates the runtime.
host
: function SvgHost(props: BackendHostProps<ReactNode>): JSX.ElementSvgHost,
stages?: readonly Readonly<{
    kinds: readonly AnyEffectKind[];
    stage: EffectStage<ReactNode>;
    node?: NodeEffectPainter;
}>[] | undefined
The stages this binding paints effects with, each with the kinds it paints. A kind names no stage; the backend pairs it here, so the same kind paints through a React stage in one runtime and a Vue stage in another. Every listed stage is mounted around every actor, even with no effect of its kinds applied, so it can paint actor-owned passes. Effects of a kind no stage lists are left to the host.
stages
: [{ kinds: readonly AnyEffectKind[]
Every kind must share one raster: the stage's place in the wrap order.
kinds
: [
const svgFilter: EffectKind<"svg", "filter", SvgFilterTypes, {
    readonly kind: "inline";
}, "actor">
svgFilter
], stage: EffectStage<ReactNode>stage: const svgFilterStage: EffectStage<ReactNode>
Each effect gets its own filter, wrapped around the ones before it.
svgFilterStage
}],
});

FrameScope holds the frame the host paints. Each ActorRendererOutlet reads its actor from it by id, finds the renderer for the actor, draws it, and draws the actors hosted in its slots through the same outlet. An outlet draws again only when its actor changed, or when what its renderer read did (frame, state, or a slot entry’s actor), so wrapActor should be one function across frames. wrapActor runs for each of them, so a slot child gets its own <g> inside its parent’s. A child’s localLayout is relative to its parent, which is how nested SVG transforms compose.

stages pairs each stage with the effect kinds it paints: a kind names no stage, so each binding’s backend supplies its own. The runtime orders them into effectStages, innermost first, and groupEffectsByStage hands each stage the actor’s effects it owns. place lets every stage wrap the actor’s content in turn. The host knows nothing about filters: Effect kinds builds svgFilter and its stage.

BackendHostProps
actors The layer’s painted roots, in paint order. Slot children are reached through them
state The whole frame: every actor by id, layers, camera
frame, compiled The frame number and the compiled scene
registry The renderers this backend can draw with
effectStages The backend’s effect stages, innermost first
width, height The layer’s size in pixels
layer The id of the layer it paints, the one its actors carry; null for the default layer

Opacity maps to an attribute. Blend modes, isolation and clipping need an offscreen group this host does not build, so it calls assertSubtreeCompositingSupported first. An actor that asks for one fails the frame with an error naming the actor, the backend and what it asked for, instead of drawing without it.

Renderers

Renderers for the backend come from its render, as on the built-in backends. The row draws nothing of its own; its slot already holds an element for each dot:

import { const createRenderRuntime: (input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>) => ReactRenderRuntime
The 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 svg: StagedBackend<(props: BackendHostProps<React.ReactNode>) => JSX.Element, "svg", React.ReactNode>svg } from "./svg-host"; import { class DotDot, class RowRow } from "./svg-dots.scene"; const const dot: BackendRendererDefinition<readonly [typeof Dot], "svg">dot = const svg: StagedBackend<(props: BackendHostProps<React.ReactNode>) => JSX.Element, "svg", React.ReactNode>svg.render: <typeof Dot>(blueprint: typeof Dot, render: ActorRendererForBlueprints<readonly [typeof Dot], "svg">, options?: ActorRendererRegistrationOptions) => BackendRendererDefinition<readonly [typeof Dot], "svg"> (+1 overload)render(class DotDot, ({
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;
    fill: string;
    layout: {
        x: LayoutValue;
        y: LayoutValue;
        width: number | `${number}%` | Readonly<{
            kind: "layout.percent";
            value: number;
        }>;
        height: number | ... 1 more ... | Readonly<...>;
    };
    ... 5 more ...;
    transitionState: {
        ...;
    };
} & {
    ...;
}
The actor. Reading it makes the renderer depend on all of it.
actor
}) => {
const { const width: numberwidth, const height: numberheight } =
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;
    fill: string;
    layout: {
        x: LayoutValue;
        y: LayoutValue;
        width: number | `${number}%` | Readonly<{
            kind: "layout.percent";
            value: number;
        }>;
        height: number | ... 1 more ... | Readonly<...>;
    };
    ... 5 more ...;
    transitionState: {
        ...;
    };
} & {
    ...;
}
The actor. Reading it makes the renderer depend on all of it.
actor
.ResolvedActor.localLayout: Readonly<Rect>localLayout;
return ( <React.JSX.IntrinsicElements.circle: React.SVGProps<SVGCircleElement>circle React.SVGAttributes<SVGCircleElement>.cx?: string | number | undefinedcx={const width: numberwidth / 2} React.SVGAttributes<SVGCircleElement>.cy?: string | number | undefinedcy={const height: numberheight / 2} React.SVGAttributes<SVGCircleElement>.r?: string | number | undefinedr={var Math: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math
.Math.min(...values: number[]): number
Returns the smaller of a set of supplied numeric expressions.
@paramvalues Numeric expressions to be evaluated.
min
(const width: numberwidth, const height: numberheight) / 2}
React.SVGAttributes<SVGCircleElement>.fill?: string | undefinedfill={
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;
    fill: string;
    layout: {
        x: LayoutValue;
        y: LayoutValue;
        width: number | `${number}%` | Readonly<{
            kind: "layout.percent";
            value: number;
        }>;
        height: number | ... 1 more ... | Readonly<...>;
    };
    ... 5 more ...;
    transitionState: {
        ...;
    };
} & {
    ...;
}
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;
    fill: string;
    layout: {
        x: LayoutValue;
        y: LayoutValue;
        width: number | `${number}%` | Readonly<{
            kind: "layout.percent";
            value: number;
        }>;
        height: number | ... 1 more ... | Readonly<...>;
    };
    ... 5 more ...;
    transitionState: {
        ...;
    };
}
signals
.fill: stringfill}
/> ); }); // The row draws nothing itself; each hosted dot is placed by the host. const const row: BackendRendererDefinition<readonly [typeof Row], "svg">row = const svg: StagedBackend<(props: BackendHostProps<React.ReactNode>) => JSX.Element, "svg", React.ReactNode>svg.render: <typeof Row>(blueprint: typeof Row, render: ActorRendererForBlueprints<readonly [typeof Row], "svg">, options?: ActorRendererRegistrationOptions) => BackendRendererDefinition<readonly [typeof Row], "svg"> (+1 overload)render(class RowRow, ({
slots: {
    dots: {
        actor: ResolvedActorOfInstance<Dot>;
        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
}) => (
<>{
slots: {
    dots: {
        actor: ResolvedActorOfInstance<Dot>;
        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
.
dots: {
    actor: ResolvedActorOfInstance<Dot>;
    element: RenderNode | null;
}[]
dots
.
Array<{ actor: ResolvedActorOfInstance<Dot>; element: RenderNode | null; }>.map<React.ReactNode>(callbackfn: (value: {
    actor: ResolvedActorOfInstance<Dot>;
    element: RenderNode | null;
}, index: number, array: {
    actor: ResolvedActorOfInstance<Dot>;
    element: RenderNode | null;
}[]) => React.ReactNode, thisArg?: any): React.ReactNode[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
(({ element: React.ReactNodeelement }) => element: React.ReactNodeelement)}</>
)); export const const runtime: ReactRenderRuntimeruntime = function createRenderRuntime(input: CreateRenderRuntimeInput<ReactBackendHost, React.ReactNode>): ReactRenderRuntime
The 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 svg: StagedBackend<(props: BackendHostProps<React.ReactNode>) => JSX.Element, "svg", React.ReactNode>svg], renderers?: readonly RendererRegistryEntry[] | undefined
Family 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 row: BackendRendererDefinition<readonly [typeof Row], "svg">row, const dot: BackendRendererDefinition<readonly [typeof Dot], "svg">dot],
});

The scene in the figure is a stock scene. With one backend in the runtime, the default layer uses it.

Was this page helpful?