Effects
Typed timeline records the renderer reads as overlays, with normalized progress resolved per frame.
An effect is not a mutation. It is a record on the timeline that says “this actor is under this effect, at this progress, at this frame” — and a renderer decides what that looks like.
Define
An effect belongs to one backend. It declares its params once; render is typed from them
and from the backend’s render contract, so there is no separate params type to keep in step.
import { color, colorType, defineEffect, signal, unitInterval } from "@motionactor/core";
import { dom } from "@motionactor/renderers";
export const glow = defineEffect(dom, {
name: "glow",
params: {
color: signal.of(colorType, color("#22d3ee")),
spread: signal(18),
progress: signal.of(unitInterval, 0),
},
render({ state, context, params }) {
// params: { color: Color; spread: number; progress: number }
state.overlays.push(/* a box-shadow overlay */);
},
});
Its wire identity is { backend: "dom", name: "glow" }. A DOM glow and a Pixi glow are two
values with two identities. Two effects with the same name on one backend throw when the
backend is created.
Install
A backend lists its effects next to its actor renderers, as installed code:
createBackend(dom, { renderers: [textBlockRenderer], effects: [glow, blur] });
Apply
Calling the effect with some of its params makes what ctx.apply takes; undeclared params
keep their declared defaults.
const card = ctx.spawn(Card);
const fx = ctx.apply(card, glow({ spread: 24 }));
yield* fx.play({ duration: 0.4, easing: Easing.inOutCubic });
yield* fx.spread.tween(48, 0.5); // params are signals on the handle
fx.detach();
Describe
describeEffect(glow) returns the param metadata an editor needs (kind, type, range,
default), read from the declaration.
At resolution time
compiled.at(frame) exposes the active effects on each ResolvedActor as
actor.effects: ActiveEffect[]. Renderers read this array to apply overlays.
The card above is drawn from card.effects alone: ctx.apply records the glow, play
drives its progress, a param tween widens it, and detach ends the record — the card’s
own state never changes.
const state = compiled.at(frame);
const card = state.findActor(Card);
for (const fx of card.effects) {
fx.effectType; // "glow"
fx.progress; // 0–1, normalized for this frame
fx.params; // resolved params
}