Vue
Paint scenes with Vue 3 through @motionactor/renderers/vue and @motionactor/dom/vue.
Scenes, actors and effects are the same under every binding. A Vue app changes two things:
its renderers return VNodes, and it mounts through the /vue entries.
| Package | Vue entry |
|---|---|
@motionactor/renderers |
/vue: SceneRenderer, renderers, composables, compositions, transitions, media |
@motionactor/dom |
/vue: dom (the backend), DomHost, ActorElement, vStyle, effect stages |
@motionactor/media |
/vue: views for Image, Video and Audio |
@motionactor/engine |
/vue: mountVueComposition |
@motionactor/hyperframes |
/vue: mountHyperframesComposition |
A program loads one binding: importing /vue makes every RenderNode a VNode.
Renderers
A renderer returns a VNode from h. The registry calls it as a pure function, so it calls no
composables; an actor whose view needs state renders a component.
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<DefineComponent<ExtractPropTypes<{
readonly actors: {
readonly type: PropType<BackendHostProps["actors"]>;
readonly required: true;
};
readonly state: {
readonly type: PropType<BackendHostProps["state"]>;
readonly required: true;
};
readonly frame: {
readonly type: NumberConstructor;
readonly required: true;
};
readonly compiled: {
readonly type: PropType<BackendHostProps["compiled"]>;
readonly required: true;
};
... 4 more ...;
readonly layer: {
...;
};
}>, ... 18 more ..., any>, "dom", VNodeChild>
The Vue DOM backend: `DomHost`, the `Group` primitive, and the `domStyle`
stage, as `dom` in Vue binding code: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom], renderers })`.dom, const vStyle: ObjectDirective<HTMLElement | SVGElement, StyleRecord, string, any>`v-style`: writes a style record through `applyStyle`, so a patch sets only
the declarations whose value moved, where Vue's `style` prop writes every
key on every patch. The node's only style writer: an element takes `v-style`
or `style`, never both. Writes before Vue inserts the node and at the point
of each patch Vue would patch `style`; on the server it renders the record.vStyle } from "@motionactor/dom/vue";
import { const createRenderRuntime: (input: CreateRenderRuntimeInput<VueBackendHost, VNodeChild>) => VueRenderRuntimeThe floor's `createRenderRuntime` for Vue: every backend's host is a Vue component and its stages wrap Vue nodes.createRenderRuntime } from "@motionactor/renderers/vue";
import { function h<K extends keyof HTMLElementTagNameMap>(type: K, children?: RawChildren): VNode (+22 overloads)h, function withDirectives<T extends VNode>(vnode: T, directives: DirectiveArguments): TAdds directives to a VNode.withDirectives } from "vue";
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);
}
const const badge: BackendRendererDefinition<readonly [typeof Badge], "dom">badge = const dom: StagedBackend<DefineComponent<ExtractPropTypes<{
readonly actors: {
readonly type: PropType<BackendHostProps["actors"]>;
readonly required: true;
};
readonly state: {
readonly type: PropType<BackendHostProps["state"]>;
readonly required: true;
};
readonly frame: {
readonly type: NumberConstructor;
readonly required: true;
};
readonly compiled: {
readonly type: PropType<BackendHostProps["compiled"]>;
readonly required: true;
};
... 4 more ...;
readonly layer: {
...;
};
}>, ... 18 more ..., any>, "dom", VNodeChild>
The Vue DOM backend: `DomHost`, the `Group` primitive, and the `domStyle`
stage, as `dom` in Vue binding code: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom], renderers })`.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"> & {
label: string;
shape: {
radius: number;
};
compositing: {
blend: "difference" | "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
x: LayoutValue;
y: LayoutValue;
};
... 5 more ...;
hue: number;
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor }) =>
withDirectives<VNode<RendererNode, RendererElement, {
[key: string]: any;
}>>(vnode: VNode<RendererNode, RendererElement, {
[key: string]: any;
}>, directives: DirectiveArguments): VNode<RendererNode, RendererElement, {
[key: string]: any;
}>
Adds directives to a VNode.withDirectives(h<"span">(type: "span", children?: RawChildren): VNode (+22 overloads)h("span", actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
label: string;
shape: {
radius: number;
};
compositing: {
blend: "difference" | "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
x: LayoutValue;
y: LayoutValue;
};
... 5 more ...;
hue: number;
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor.signals: Readonly<Record<string, unknown>> & {
label: string;
shape: {
radius: number;
};
compositing: {
blend: "difference" | "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
x: LayoutValue;
y: LayoutValue;
};
... 5 more ...;
hue: number;
}
signals.label: stringlabel), [
[const vStyle: ObjectDirective<HTMLElement | SVGElement, StyleRecord, string, any>`v-style`: writes a style record through `applyStyle`, so a patch sets only
the declarations whose value moved, where Vue's `style` prop writes every
key on every patch. The node's only style writer: an element takes `v-style`
or `style`, never both. Writes before Vue inserts the node and at the point
of each patch Vue would patch `style`; on the server it renders the record.vStyle, { padding: numberpadding: 8, background: stringbackground: `hsl(${actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
label: string;
shape: {
radius: number;
};
compositing: {
blend: "difference" | "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
x: LayoutValue;
y: LayoutValue;
};
... 5 more ...;
hue: number;
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor.signals: Readonly<Record<string, unknown>> & {
label: string;
shape: {
radius: number;
};
compositing: {
blend: "difference" | "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
x: LayoutValue;
y: LayoutValue;
};
... 5 more ...;
hue: number;
}
signals.hue: numberhue} 80% 45%)` }],
]),
);
export const const runtime: VueRenderRuntimeruntime = function createRenderRuntime(input: CreateRenderRuntimeInput<VueBackendHost, VNodeChild>): VueRenderRuntimeThe floor's `createRenderRuntime` for Vue: every backend's host is a Vue component and its stages wrap Vue nodes.createRenderRuntime({
backends: readonly AnyBackend<VueBackendHost, VNodeChild>[]backends: [const dom: StagedBackend<DefineComponent<ExtractPropTypes<{
readonly actors: {
readonly type: PropType<BackendHostProps["actors"]>;
readonly required: true;
};
readonly state: {
readonly type: PropType<BackendHostProps["state"]>;
readonly required: true;
};
readonly frame: {
readonly type: NumberConstructor;
readonly required: true;
};
readonly compiled: {
readonly type: PropType<BackendHostProps["compiled"]>;
readonly required: true;
};
... 4 more ...;
readonly layer: {
...;
};
}>, ... 18 more ..., any>, "dom", VNodeChild>
The Vue DOM backend: `DomHost`, the `Group` primitive, and the `domStyle`
stage, as `dom` in Vue binding code: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom], renderers })`.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 badge: BackendRendererDefinition<readonly [typeof Badge], "dom">badge],
});
A style that moves with the frame goes through v-style rather than style. Vue’s style
patch writes every key of an object on each render; v-style writes only the declarations
whose value changed, so on a frame where only hue moves the badge above sets background
alone. Numbers take px as in React, and the server renders the record as a style
attribute. An element takes one of v-style and style, never both. In an SFC that imports
vStyle in <script setup>:
<span v-style="{ padding: 8, background: `hsl(${hue} 80% 45%)` }">{{ label }}</span>
A component behind a renderer may use useHold, useMotionactorFrame and the other
composables; each reader returns a ref. A host that places its children in the browser’s flow
returns renderChild(child, { flow }) for each one. The DOM backend measures a flow child while
something reads its measuredBox.
Painting a scene
SceneRenderer resolves the frame and paints its layers. A composition’s render returns it
as a VNode:
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 } from "@motionactor/core";
import {
const defineComposition: DefineComposition<VueBackendHost, VNodeChild, VNodeChild>A composition of a `createScene` definition (`scene`), painted by `render`,
or by default by `SceneRenderer` over `background`.defineComposition,
const SceneRenderer: DefineComponent<ExtractPropTypes<{
compiled: {
type: PropType<CompiledScene<unknown>>;
required: true;
};
frame: {
type: NumberConstructor;
required: true;
};
width: {
type: NumberConstructor;
required: true;
};
height: {
type: NumberConstructor;
required: true;
};
runtime: {
type: PropType<VueRenderRuntime>;
default: undefined;
};
}>, () => VNode<RendererNode, RendererElement, {
...;
}>, ... 17 more ..., any>
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers on a stage of `width` by `height`. The
default slot is page markup, rendered in the scope of the scene's DOM layer
(`SceneLayers`), where `<actor.h1 :of="compiled.scene.card.title">` binds a
node of it to an actor; `<InLayer :layer="compiled.layers.<key>">` places
its own markup in another DOM layer.SceneRenderer,
} from "@motionactor/renderers/vue";
import { function h<K extends keyof HTMLElementTagNameMap>(type: K, children?: RawChildren): VNode (+22 overloads)h } from "vue";
import { class BadgeBadge, const runtime: VueRenderRuntimeruntime } from "./badge-renderers";
const const scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
badge: Badge;
}>
scene = createScene<{
badge: Badge;
}, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
badge: Badge;
}>, 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 badge: Badgebadge = ctx: SceneContextctx.CompileContext.spawn<Badge>(definition: ActorClass<Badge>, overrides?: {
label?: SignalInputValue<string> | undefined;
id?: undefined;
localLayout?: undefined;
worldLayout?: undefined;
compositing?: {
clip?: SignalInputValue<number | null> | undefined;
isolate?: SignalInputValue<boolean> | undefined;
blend?: SignalInputValue<"overlay" | "difference" | "hue" | "source-over" | "multiply" | "screen" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "exclusion" | "saturation" | "color" | "luminosity" | "plus-lighter"> | undefined;
} | undefined;
... 19 more ...;
bounds?: undefined;
} | undefined, options?: {
...;
} | undefined): Badge (+1 overload)
spawn(class BadgeBadge, { label?: SignalInputValue<string> | undefinedlabel: "Live" });
yield* const badge: Badgebadge.Badge.hue: ActorFieldSignal<number>hue.LiveSignal<number>.tween(target: number, options: number | SignalTweenOptions<number>): Generator<Frame, void, unknown>Tween to target over authored seconds — must yield*tween(320, 1);
return { badge: Badgebadge };
});
export const const badgeCard: CompositionDefinition<HostInputArgs<Readonly<Record<never, SceneInput>>>, {
badge: Badge;
}, VueBackendHost, VNodeChild, VNodeChild>
badgeCard = defineComposition<SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
badge: Badge;
}>>(definition: SceneCompositionInput<SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
badge: Badge;
}>, VueBackendHost, VNodeChild, VNodeChild>): CompositionDefinition<...> (+1 overload)
`scene`'s inputs are the props.defineComposition({
id: stringid: "BadgeCard",
width: numberwidth: 1280,
height: numberheight: 720,
fps: numberfps: 30,
defaultProps?: HostInputArgs<Readonly<Record<never, SceneInput>>> | undefineddefaultProps: {},
runtime: RenderRuntime<VueBackendHost, VNodeChild>runtime,
scene: SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, {
badge: Badge;
}>
scene,
render?(args: CompositionRenderArgs<HostInputArgs<Readonly<Record<never, SceneInput>>>, {
badge: Badge;
}>): VNodeChild
render: ({ compiled: CompiledScene<{
badge: Badge;
}, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>
compiled, frame: numberframe, width: numberwidth, height: numberheight }) =>
h<Partial<{
runtime: VueRenderRuntime;
}> & Omit<{
readonly compiled: CompiledScene<unknown, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>;
readonly frame: number;
readonly width: number;
readonly height: number;
readonly runtime?: VueRenderRuntime | undefined;
} & VNodeProps & AllowedComponentProps & ComponentCustomProps, "runtime">>(type: Constructor<Partial<{
runtime: VueRenderRuntime;
}> & Omit<...>>, props?: (VNodeProps & ... 3 more ... & Omit<...>) | undefined, children?: RawChildren | RawSlots): VNode (+22 overloads)
h(const SceneRenderer: DefineComponent<ExtractPropTypes<{
compiled: {
type: PropType<CompiledScene<unknown>>;
required: true;
};
frame: {
type: NumberConstructor;
required: true;
};
width: {
type: NumberConstructor;
required: true;
};
height: {
type: NumberConstructor;
required: true;
};
runtime: {
type: PropType<VueRenderRuntime>;
default: undefined;
};
}>, () => VNode<RendererNode, RendererElement, {
...;
}>, ... 17 more ..., any>
Frame-passive renderer for a CompiledScene: resolves
the frame and paints its layers on a stage of `width` by `height`. The
default slot is page markup, rendered in the scope of the scene's DOM layer
(`SceneLayers`), where `<actor.h1 :of="compiled.scene.card.title">` binds a
node of it to an actor; `<InLayer :layer="compiled.layers.<key>">` places
its own markup in another DOM layer.SceneRenderer, { compiled: CompiledScene<unknown, Readonly<Record<string, CompiledLayer<BackendRef<string>>>>>compiled, frame: numberframe, width: numberwidth, height: numberheight }),
});
Outside a composition, provideRenderRuntime, provideFrameSource and provideFrameGate set
the runtime, an ExternalFrameSource and a PromiseFrameGate for everything below them.
Transitions
Transition takes the actor and a default slot. Each Shared is tagged by name, so it morphs
between the actor’s views. With the Standings actor from Transitions:
import { const dom: StagedBackend<DefineComponent<ExtractPropTypes<{
readonly actors: {
readonly type: PropType<BackendHostProps["actors"]>;
readonly required: true;
};
readonly state: {
readonly type: PropType<BackendHostProps["state"]>;
readonly required: true;
};
readonly frame: {
readonly type: NumberConstructor;
readonly required: true;
};
readonly compiled: {
readonly type: PropType<BackendHostProps["compiled"]>;
readonly required: true;
};
... 4 more ...;
readonly layer: {
...;
};
}>, ... 18 more ..., any>, "dom", VNodeChild>
The Vue DOM backend: `DomHost`, the `Group` primitive, and the `domStyle`
stage, as `dom` in Vue binding code: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom], renderers })`.dom } from "@motionactor/dom/vue";
import { const Shared: DefineComponent<ExtractPropTypes<{
name: {
type: PropType<SharedName>;
required: true;
};
style: {
type: PropType<StyleRecord>;
default: undefined;
};
reflow: {
type: BooleanConstructor;
default: boolean;
};
stacking: {
type: PropType<SharedStacking>;
default: string;
};
}>, () => VNode<RendererNode, RendererElement, {
[key: string]: any;
}>, {}, {}, {}, ... 14 more ..., any>
Tags a shared element by a declared name — the one way a view marks
something `Transition` should morph; the kind (box or text) rides the
declaration. Chrome belongs on the tagged element itself: that is what is
measured and morphed.
The default slot is the content. With `reflow`, it is content that lays
itself out at any box — a container's hosted child — and receives `{ box }`:
the morphing box inside a ghost, the measured box in a layer, `null` on the
measuring commit. Without it the content is fixed and a ghost scales a copy.Shared, const Transition: DefineComponent<ExtractPropTypes<{
actor: {
type: PropType<TransitioningActor>;
required: true;
};
}>, () => VNode<RendererNode, RendererElement, {
[key: string]: any;
}>, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, ToResolvedProps<ExtractPropTypes<{
actor: {
type: PropType<TransitioningActor>;
required: true;
};
}>, {}>, ... 8 more ..., any>
The binding for `LayoutActor.transition(apply, options)`. The default slot
renders one layer from the actor resolved for it: the snapshot one frame
before the mutation for the from-layer, the current actor for the to-layer.
Requires the compiled scene (`provideCompiledScene`, as `SceneRenderer`
provides it); without one it shows the current tree.
One measuring commit per transition, latched by transition and channel run:
the layers render untransformed, every tagged element publishes its natural
box, the scene re-resolves and the next render paints the morph. The latch
is a plain variable so flipping it never re-renders on its own.Transition } from "@motionactor/renderers/vue";
import { function h<K extends keyof HTMLElementTagNameMap>(type: K, children?: RawChildren): VNode (+22 overloads)h } from "vue";
import { class StandingsStandings } from "./standings";
const { const row: (name: string) => SharedName<"box">row } = class StandingsStandings.Standings.shared: SharedNames<{
readonly row: (name: string) => string;
}>
shared;
export const const standingsView: BackendRendererDefinition<readonly [typeof Standings], "dom">standingsView = const dom: StagedBackend<DefineComponent<ExtractPropTypes<{
readonly actors: {
readonly type: PropType<BackendHostProps["actors"]>;
readonly required: true;
};
readonly state: {
readonly type: PropType<BackendHostProps["state"]>;
readonly required: true;
};
readonly frame: {
readonly type: NumberConstructor;
readonly required: true;
};
readonly compiled: {
readonly type: PropType<BackendHostProps["compiled"]>;
readonly required: true;
};
... 4 more ...;
readonly layer: {
...;
};
}>, ... 18 more ..., any>, "dom", VNodeChild>
The Vue DOM backend: `DomHost`, the `Group` primitive, and the `domStyle`
stage, as `dom` in Vue binding code: `dom.render(Box, …)`,
`createRenderRuntime({ backends: [dom], renderers })`.dom.render: <typeof Standings>(blueprint: typeof Standings, render: ActorRendererForBlueprints<readonly [typeof Standings], "dom">, options?: ActorRendererRegistrationOptions) => BackendRendererDefinition<readonly [typeof Standings], "dom"> (+1 overload)render(
class StandingsStandings,
({ actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
shape: {
radius: number;
};
compositing: {
blend: "difference" | "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
x: LayoutValue;
y: LayoutValue;
};
... 5 more ...;
teams: Team[];
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor }) =>
h<Partial<{}> & Omit<{
readonly actor: TransitioningActor;
} & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>>(type: Constructor<Partial<{}> & Omit<{
readonly actor: TransitioningActor;
} & VNodeProps & AllowedComponentProps & ComponentCustomProps, never>>, props?: (VNodeProps & {
__v_isVNode?: never;
[Symbol.iterator]?: never;
} & Record<...> & Partial<...> & Omit<...>) | undefined, children?: RawChildren | RawSlots): VNode (+22 overloads)
h(const Transition: DefineComponent<ExtractPropTypes<{
actor: {
type: PropType<TransitioningActor>;
required: true;
};
}>, () => VNode<RendererNode, RendererElement, {
[key: string]: any;
}>, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, ToResolvedProps<ExtractPropTypes<{
actor: {
type: PropType<TransitioningActor>;
required: true;
};
}>, {}>, ... 8 more ..., any>
The binding for `LayoutActor.transition(apply, options)`. The default slot
renders one layer from the actor resolved for it: the snapshot one frame
before the mutation for the from-layer, the current actor for the to-layer.
Requires the compiled scene (`provideCompiledScene`, as `SceneRenderer`
provides it); without one it shows the current tree.
One measuring commit per transition, latched by transition and channel run:
the layers render untransformed, every tagged element publishes its natural
box, the scene re-resolves and the next render paints the morph. The latch
is a plain variable so flipping it never re-renders on its own.Transition, { actor: TransitioningActoractor }, {
default: (table: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
shape: {
radius: number;
};
compositing: {
blend: "difference" | "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
x: LayoutValue;
y: LayoutValue;
};
... 5 more ...;
teams: Team[];
} & {
...;
}) => VNode<...>
default: (table: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
shape: {
radius: number;
};
compositing: {
blend: "difference" | "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
x: LayoutValue;
y: LayoutValue;
};
... 5 more ...;
teams: Team[];
} & {
...;
}
table: typeof actor: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
shape: {
radius: number;
};
compositing: {
blend: "difference" | "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
x: LayoutValue;
y: LayoutValue;
};
... 5 more ...;
teams: Team[];
} & {
...;
}
The actor. Reading it makes the renderer depend on all of it.actor) =>
h<"ol">(type: "ol", children?: RawChildren): VNode (+22 overloads)h("ol", table: ResolvedActor & Omit<ResolvedActor, "signals" | "channels"> & {
shape: {
radius: number;
};
compositing: {
blend: "difference" | "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
x: LayoutValue;
y: LayoutValue;
};
... 5 more ...;
teams: Team[];
} & {
...;
}
table.signals: Readonly<Record<string, unknown>> & {
shape: {
radius: number;
};
compositing: {
blend: "difference" | "source-over" | "multiply" | "screen" | "overlay" | "darken" | "lighten" | "color-dodge" | "color-burn" | "hard-light" | "soft-light" | "exclusion" | "hue" | "saturation" | "color" | "luminosity" | "plus-lighter";
isolate: boolean;
clip: number | null;
};
measuredBox: Rect | undefined;
layout: {
width: number | `${number}%` | Readonly<{
kind: "layout.percent";
value: number;
}>;
height: number | ... 1 more ... | Readonly<...>;
x: LayoutValue;
y: LayoutValue;
};
... 5 more ...;
teams: Team[];
}
signals.teams: Team[]teams.Array<Team>.map<VNode<RendererNode, RendererElement, {
[key: string]: any;
}>>(callbackfn: (value: Team, index: number, array: Team[]) => VNode<RendererNode, RendererElement, {
[key: string]: any;
}>, thisArg?: any): VNode<RendererNode, RendererElement, {
[key: string]: any;
}>[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.map((team: Teamteam) =>
h<Partial<{
style: StyleRecord;
reflow: boolean;
stacking: SharedStacking;
}> & Omit<{
readonly name: SharedName<ViewTransitionElementKind>;
readonly reflow: boolean;
readonly stacking: SharedStacking;
readonly style?: StyleRecord | undefined;
} & VNodeProps & AllowedComponentProps & ComponentCustomProps, DefaultKeys<{
name: {
type: PropType<SharedName>;
required: true;
};
style: {
...;
};
reflow: {
...;
};
stacking: {
...;
};
}>>>(type: Constructor<...>, props?: (VNodeProps & ... 3 more ... & Omit<...>) | undefined, children?: RawChildren | RawSlots): VNode (+22 overloads)
h(
const Shared: DefineComponent<ExtractPropTypes<{
name: {
type: PropType<SharedName>;
required: true;
};
style: {
type: PropType<StyleRecord>;
default: undefined;
};
reflow: {
type: BooleanConstructor;
default: boolean;
};
stacking: {
type: PropType<SharedStacking>;
default: string;
};
}>, () => VNode<RendererNode, RendererElement, {
[key: string]: any;
}>, {}, {}, {}, ... 14 more ..., any>
Tags a shared element by a declared name — the one way a view marks
something `Transition` should morph; the kind (box or text) rides the
declaration. Chrome belongs on the tagged element itself: that is what is
measured and morphed.
The default slot is the content. With `reflow`, it is content that lays
itself out at any box — a container's hosted child — and receives `{ box }`:
the morphing box inside a ghost, the measured box in a layer, `null` on the
measuring commit. Without it the content is fixed and a ghost scales a copy.Shared,
{ key?: PropertyKey | undefinedkey: team: Teamteam.name: stringname, name: SharedName<ViewTransitionElementKind>A name built by the actor's `defineShared` declaration; never a string.name: const row: (name: string) => SharedName<"box">row(team: Teamteam.name: stringname) },
() => `${team: Teamteam.name: stringname} ${team: Teamteam.points: numberpoints}`,
))),
}),
);
Effects
domStyle effects, overlays included, paint unchanged. Canvas effects such as glShader
need the browser’s HTML-in-canvas; call provideHtmlCanvas(nativeHtmlCanvas) once near the
root. Where the browser lacks it, the content paints unchanged under an overlay naming the
missing browser APIs.
Hosts
mountVueComposition from @motionactor/engine/vue mounts a composition as the native
preview, with the controller Hosts describes.
@motionactor/hyperframes/vue mounts it for HyperFrames. Headless capture reads the binding from
the composition:
import { function openHeadlessSession(options: HeadlessSessionOptions): Promise<HeadlessSession>Open a composition in headless Chromium. The page is the one Studio previews
(`buildCompositionPreview`), served from loopback; frames come out through
CDP screenshots (`@motionactor/engine/capture/cdp`). An abort or a failure
while opening releases everything it had started, the browser included.openHeadlessSession } from "@motionactor/engine/node";
const const session: HeadlessSessionsession = await function openHeadlessSession(options: HeadlessSessionOptions): Promise<HeadlessSession>Open a composition in headless Chromium. The page is the one Studio previews
(`buildCompositionPreview`), served from loopback; frames come out through
CDP screenshots (`@motionactor/engine/capture/cdp`). An abort or a failure
while opening releases everything it had started, the browser included.openHeadlessSession({
HeadlessSessionOptions.entry: CompositionEntryThe composition, as `bundleComposition`/`buildCompositionPreview` take it.entry: {
CompositionEntry.entry: stringentry: "./src/badge-card.ts",
CompositionEntry.export: stringexport: "badgeCard",
},
});
const const jpeg: Uint8Array<ArrayBufferLike>jpeg = await const session: HeadlessSessionsession.HeadlessSession.still(frame: number, options?: HeadlessStillOptions): Promise<Uint8Array>A JPEG of `frame`. Captures only that frame.still(30);
await const session: HeadlessSessionsession.HeadlessSession.close(): Promise<void>Waits for the running operation, then releases the browser, server and build. Idempotent.close();