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.useCallback, type type ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<ReactNode> | React.ReactPortal | Promise<AwaitedReactNode> | null | undefinedRepresents all of the things React can render.
Where
{@link
ReactElement
}
only represents JSX, `ReactNode` represents everything that can be rendered.ReactNode } from "react";
import {
const assertSubtreeCompositingSupported: (roots: readonly ResolvedActor[], actors: ReadonlyMap<string, ResolvedActor>, backend: BackendRef) => voidAssert 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 | undefinedRepresents all of the things React can render.
Where
{@link
ReactElement
}
only represents JSX, `ReactNode` represents everything that can be rendered.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): voidAssert 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.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 | undefinedRepresents all of the things React can render.
Where
{@link
ReactElement
}
only represents JSX, `ReactNode` represents everything that can be rendered.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) => ReactNodeA 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: ResolvedCompositingResolved from the `compositing` group and `appearance.opacity`; identity
when the actor declares neither, so a backend never reconstructs it from
`signals`.compositing.ResolvedCompositing.opacity: numberFolded 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.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: stringThe 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) | undefinedBackend-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.ElementWhatever 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>) => 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 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: MathAn intrinsic object that provides basic mathematics functionality and constants.Math.Math.min(...values: number[]): numberReturns the smaller of a set of supplied numeric expressions.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.map(({ element: React.ReactNodeelement }) => element: React.ReactNodeelement)}</>
));
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 svg: StagedBackend<(props: BackendHostProps<React.ReactNode>) => JSX.Element, "svg", React.ReactNode>svg],
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 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.