raea/animation
Declared animation: named moves, staggers, sequences and timelines, and a model's own clips.
raea/animation describes motion as data instead of per-frame code. A move (fade, rise, pop, slide, slam,
wipe and the rest) says how something enters or leaves; sequence, parallel and stagger combine moves; and
<Animate> plays a declared animation on what it wraps. Because the motion is data, the engine can play it without
running React every frame, and a lint can read it. useAnimation3d plays a glTF model's own clips.
import { Animate, Stagger, pop } from "raea/animation";
// each line rises in 4 frames after the one before, and fades out at frame 80
function Lines({ lines }: { lines: string[] }) {
return (
<Stagger each={4} enter="rise" exit="fade" exitAt={80}>
{lines.map((l) => <p key={l}>{l}</p>)}
</Stagger>
);
}
// one element, with a move built in code
const Badge = () => <Animate enter={pop({ frames: 10 })}>NEW</Animate>;import { Animate, clipEvents, curve, curveFn } from "raea/animation"raea/animation exports 2 components, 1 hook, 27 functions, 2 values, 18 types. Bevy's counterpart: bevy_animation. Source: sdk/raea/src/animation.ts.
Components
Animate
Animate.fingerprint for a runtime's own bailout. Its declaration is the engine's
Animation (@bevy raea_core_animate::spec::Animation), typed from the registry (animate.gen.ts), sent as data-raea-anim and
read back as that type by sdk/react animate.rs.
Also takes the props of AnimateOwnProps.
Stagger
Each child in its own each frames apart (or by grid distance). The same content as last
render gives the same element back.
Also takes the props of StaggerProps (each, grid, exitEach); AnimateOwnProps.
Hooks
useAnimation3d
For a 3D node (
function useAnimation3d(spec: Record<string, TrackSpec<string | number> | TrackSpec<string | number>[] | Keys> | Move | undefined): { attr?: string; values?: Record<string, string | number> | undefined; }Functions
clipEvents
The clip events that fire between two frames (Bevy's AnimationClip events, as data): an event time seconds into
the clip fires on the frame whose clip time first reaches it, that is when it lies in (prev, now], once per loop
when the clip loops (duration its length). Give the previous frame's clip time and this frame's, as a time runs; on the first frame pass prev -1 so an event at 0 fires.
function clipEvents<E extends { time: number; }>(events: E[], prev: number, now: number, duration: number, loop?: boolean): E[]curveFn
A curve name as a function (the engine's easing::Easing::parse, the same names).
function curveFn(name: string, fps?: number): (t: number) => numberdelay
The same move later by frames.
function delay(m: MoveLike, frames: number): Movefade
opacity 0 to 1 over frames (6).
function fade(o?: { frames?: number; ease?: Curve; }): Movefill
a bar filling: width 0 to to (100%) over frames (24), out-cubic.
function fill(o?: { to?: number; frames?: number; unit?: "px" | "%"; ease?: Curve; }): Movefingerprint
FNV-1a over a value's numbers and strings. Functions (components, handlers) count by identity, React elements by
type, key and props; an inline animate={{ y: track(...) }} or style={{...}} is a new object every render, and this
tells it's the same content. Assumes props aren't mutated in place (React's own assumption).
function fingerprint(v: unknown, h: number, depth?: number): numbergridDelay
A grid item's distance from an origin, in steps (raea-animate's StaggerPattern::Grid, the same rule).
function gridDelay(cols: number, rows: number, from: GridFrom, index: number): numbermove
A move from its tracks, by property: move({ opacity: track({ 0: 0, 6: 1 }), y: track({ 0: 30, 6: 0 }) }). Its
length is its last key. The named moves (rise, pop and the rest) are built with it.
function move(tracks: Record<string, TrackSpec<string | number> | TrackSpec<string | number>[] | Keys<string | number>>): Moveparallel
Every step at once.
function parallel(...steps: Step[]): Grouppop
scale from (0.6), peak (1.08) at frames/2, 1 at frames (8); in over 3 frames.
function pop(o?: { from?: number; peak?: number; frames?: number; ease?: Curve; }): MovepropsFingerprint
A props object's fingerprint: what Animate.fingerprint).
function propsFingerprint(props: object): numberpushIn
a slow push-in on footage: scale 1 to 1 + amount (0.04) across frames (90), linear.
function pushIn(o?: { amount?: number; frames?: number; }): Movereverse
A move played backwards: it leaves the way it came (each curve reversed, so an out-curve becomes the matching in-curve). An exit is its entrance reversed.
function reverse(m: MoveLike): Moverise
y offset distance to 0 over frames (30 px over 6), fading in over fade (5).
function rise(o?: { distance?: number; frames?: number; fade?: number; ease?: Curve; }): MovesampleAnimation
What an Animation sets at composition frame frame: page style keys, a counter's text, or 3D props
(raea-animate's Sampled, here for plain bun).
function sampleAnimation(a: Animation, frame: number): { style: Record<string, string | number>; text?: string; values: Record<string, string | number>; }sampleMove
What a move sets at local frame frame when it starts at start: the same rules the engine samples
function sampleMove(m: MoveLike, start: number, frame: number, fps?: number): Record<string, string | number>sequence
Each step when the one before it ends (gap frames apart: sequenceGap).
function sequence(...steps: Step[]): GroupsequenceGap
Each step when the one before it ends, gap frames later (a negative gap overlaps them).
function sequenceGap(gap: number, ...steps: Step[]): Groupslam
a hit: scale from (1.3), dip (0.96) at frames/2, 1 at frames (8); in over 2 frames.
function slam(o?: { from?: number; dip?: number; frames?: number; ease?: Curve; }): Moveslide
arrives from a side: distance px (30) over frames (7), fading in over 5. from is where it comes from.
function slide(o?: { from?: Dir; distance?: number; frames?: number; ease?: Curve; }): Movestagger
The parts each frames apart, each with m. With grid, delays spread by grid distance from an origin.
function stagger(parts: string[], each: number, m: MoveLike, grid?: { cols: number; rows?: number; from?: GridFrom; } | undefined): Groupstrike
a strike-through line drawn: width 0 to 100% over frames (8).
function strike(o?: { frames?: number; ease?: Curve; }): Movetick
a number counting from from (0) to to over frames (24), written as the element's text: digits decimals,
separator between its thousands ("," or true writes 12,345; none by default), prefix and suffix around it
("quot;, " pts"). Hidden until it starts, then in over fade frames (2; 0 shows it at from the whole time).
function tick(o: { to: number; from?: number; frames?: number; digits?: number; ease?: Curve; prefix?: string; suffix?: string; separator?: string | boolean; fade?: number; }): Movetimeline
Steps placed in time (a sequence of them). Each part's start is decided here, once.
function timeline(...steps: Step[]): PlantoMove
A name, a move or several moves together, as one move.
function toMove(m: MoveLike): Movetrack
A track: track({ 0: 30, 6: 0 }) is a 30 px rise over 6 frames, out-cubic by default.
function track<V = string | number>(keys: Keys<V>, ease?: Curve | Curve[], opts?: Omit<TrackSpec<string | number>, "ease" | "keys">): TrackSpec<V>wipe
a reveal wiping across from from (left) over frames (8), in-out-cubic, by clipping.
function wipe(o?: { from?: Dir; frames?: number; ease?: Curve; }): MoveValues
curve
Curve helpers: curve.bezier(0.05, 0.7, 0.1, 1), curve.spring({ damping: 15, stiffness: 120 }).
moves
The named moves by name, as enter="rise" finds them.
Types
AnimateOwnProps
| Field | Type | What it is |
|---|---|---|
enter | MoveLike | the entrance: a move's name, a move, or several together |
exit | MoveLike | the exit, played at exitAt (an entrance reversed: exit="rise" sinks back out) |
animate | Record<string, TrackSpec | TrackSpec[] | Keys> | Move | free tracks by property, in local frames: { backgroundColor: track({ 0: "#fff", 20: "#000" }) } |
at | number | local frame the entrance starts (0) |
exitAt | number | local frame the exit starts |
timeline | Plan | a timeline's plan and this element's part in it: the part's move, at its start (plus at) |
part | string | the part in `timeline` whose move this element plays |
mount | "during" | "always" | "always" (default): in the tree, and its layout, the whole time (invisible before an entrance that fades in); "during": only from the entrance's start until the exit ends (it takes its space when it arrives) |
as | string | the element: "div" by default |
style | CSSProperties | inline style |
className | string | Tailwind or CSS classes |
children | ReactNode | what moves |
AnimateProps
type AnimateProps = AnimateOwnProps & { [attr: string]: unknown }Animation
One element's declared animation.
Rust: raea_core_animate::spec::Animation.
| Field | Type | What it is |
|---|---|---|
origin (required) | number | the composition frame the element's local frame 0 falls on (where its Sequence starts) |
freeze (required) | number | null | a local frame to hold at whatever the composition's frame (inside a Freeze) |
fps (required) | number | the frames per second its frames count; springs measure their settle time with it (0: the clock's) |
tracks (required) | (AnimTrack)[] | the element's keyframed properties, one track each |
show_from (required) | number | null | the local frame it's shown from, when it shows only for a window (mount="during"); before it, display none |
show_until (required) | number | null | the local frame it's hidden from again |
AnimGraphNode
A node of a
type AnimGraphNode = | { clip: string | number; time?: number; weight?: number; mask?: string[] } | { blend: AnimGraphNode[]; weight?: number; mask?: string[] } | { add: AnimGraphNode[]; weight?: number; mask?: string[] }AnimKey
One key: a value at a local frame, and the curve from it to the next key.
Rust: raea_core_animate::spec::AnimKey.
| Field | Type | What it is |
|---|---|---|
at (required) | number | the local frame it's at |
value (required) | number | a number property's value there |
color (required) | string | a colour property's value (CSS text); empty for numbers |
ease (required) | string | the curve to the next key (easing::Easing::parse); empty is linear |
AnimTrack
Keyframes on one property.
Rust: raea_core_animate::spec::AnimTrack.
| Field | Type | What it is |
|---|---|---|
prop (required) | string | the property it keys: opacity, x, y, z, scale, rotate, width, height, blur, clip, a colour, text, or another number |
keys (required) | (AnimKey)[] | its keyframes in frame order |
unit (required) | string | a length's unit: "px" (when empty) or "%" |
repeat (required) | string | past the last key: "" or "hold", "loop", "pingpong" (for ever), or "loop:N" / "pingpong:N" |
digits (required) | number | decimals a text number is written with |
prefix (required) | string | written before and after a text number (a counter's "quot; and " pts") |
suffix (required) | string | written after a text number |
separator (required) | string | between a text number's thousands ("," writes 12,345); empty: none |
Curve
type Curve = CurveName | (string & {})CurveName
Curve names. Any of these, or bezier(x1,y1,x2,y2), steps(n), spring(mass,damping,stiffness),
reverse(<curve>).
type CurveName = | "linear" | "ease" | `${"in" | "out" | "in-out"}-${"quad" | "cubic" | "quart" | "quint" | "sine" | "expo" | "circ" | "back" | "elastic" | "bounce"}`GridFrom
type GridFrom = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "center" | "top" | "bottom" | "left" | "right"Group
| Field | Type | What it is |
|---|---|---|
kind (required) | "sequence" | "parallel" | sequence: one after another; parallel: together |
steps (required) | Step[] | its steps, in order |
gap (required) | number | frames between steps of a sequence (negative overlaps) |
Keys
Keyframes by local frame: numbers, or colour text for colour properties (a material's animate takes other
values for its own fields, LinearRgba for an emissive).
type Keys = Record<number, V>Move
Tracks by property, in frames from the move's own start.
| Field | Type | What it is |
|---|---|---|
tracks (required) | Record<string, TrackSpec | TrackSpec[]> | keyframes by property, in frames from the move's start |
frames (required) | number | how long it runs (its last key) |
MoveLike
type MoveLike = MoveName | Move | MoveLike[]MoveName
type MoveName = "rise" | "pop" | "slam" | "slide" | "wipe" | "fill" | "strike" | "pushIn" | "fade"Plan
A timeline's plan: each part's start frame and move, and the whole length.
| Field | Type | What it is |
|---|---|---|
parts (required) | Record<string, { at: number; move: Move }> | each part's start frame and move, by its name |
frames (required) | number | the whole timeline's length in frames |
StaggerProps
| Field | Type | What it is |
|---|---|---|
each (required) | number | frames between one child's entrance and the next |
grid | { cols: number; rows?: number; from?: GridFrom } | spread by grid distance instead of order |
exitEach | number | the exit staggered too, exitEach frames apart (0: all together) |
style | CSSProperties | inline style |
animate | Record<string, TrackSpec | TrackSpec[] | Keys> | Move | free tracks by property, in local frames: { backgroundColor: track({ 0: "#fff", 20: "#000" }) } |
as | string | the element: "div" by default |
exit | MoveLike | the exit, played at exitAt (an entrance reversed: exit="rise" sinks back out) |
children | ReactNode | what moves |
enter | MoveLike | the entrance: a move's name, a move, or several together |
at | number | local frame the entrance starts (0) |
exitAt | number | local frame the exit starts |
mount | "during" | "always" | "always" (default): in the tree, and its layout, the whole time (invisible before an entrance that fades in); "during": only from the entrance's start until the exit ends (it takes its space when it arrives) |
className | string | Tailwind or CSS classes |
Step
A timeline step: [part, move], a group from sequence/parallel/stagger, or a pause in frames.
type Step = [part: string, move: MoveLike] | { part: string; move: MoveLike; offset?: number } | Group | numberTrackSpec
One property's keyframes: values by frame, the curve between each pair (one for all, or one per segment).
| Field | Type | What it is |
|---|---|---|
keys (required) | [number, V][] | [frame, value] pairs in frame order, frames from the move's start |
ease (required) | Curve | Curve[] | the curve between keys: one for all, or one a segment |
unit | "px" | "%" | a length's unit: px (the default) or % |
repeat | "hold" | "loop" | "pingpong" | ${"loop" | "pingpong"}:${number}`` | past the last key: hold (the default), loop or pingpong, for ever or N times ("loop:3") |
digits | number | decimals a text counter writes |
prefix | string | a text counter's text before and after the number, and between its thousands ("," writes 12,345) |
suffix | string | written after a text counter's number (" pts") |
separator | string | between a text counter's thousands ("," writes 12,345) |