raea/animation

Declared animation: named moves, staggers, sequences and timelines, and a model's own clips.

View as Markdown

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.

TSX
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>;
TS
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

, memoised by its props' content (React skips a render whose props fingerprint matches the last). The fingerprint is on it as 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 , entrances 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 (, materials): the declaration's text, or under plain bun the props it sets now.

TS
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 's time runs; on the first frame pass prev -1 so an event at 0 fires.

TS
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).

TS
function curveFn(name: string, fps?: number): (t: number) => number

delay

The same move later by frames.

TS
function delay(m: MoveLike, frames: number): Move

fade

opacity 0 to 1 over frames (6).

TS
function fade(o?: { frames?: number; ease?: Curve; }): Move

fill

a bar filling: width 0 to to (100%) over frames (24), out-cubic.

TS
function fill(o?: { to?: number; frames?: number; unit?: "px" | "%"; ease?: Curve; }): Move

fingerprint

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).

TS
function fingerprint(v: unknown, h: number, depth?: number): number

gridDelay

A grid item's distance from an origin, in steps (raea-animate's StaggerPattern::Grid, the same rule).

TS
function gridDelay(cols: number, rows: number, from: GridFrom, index: number): number

move

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.

TS
function move(tracks: Record<string, TrackSpec<string | number> | TrackSpec<string | number>[] | Keys<string | number>>): Move

parallel

Every step at once.

TS
function parallel(...steps: Step[]): Group

pop

scale from (0.6), peak (1.08) at frames/2, 1 at frames (8); in over 3 frames.

TS
function pop(o?: { from?: number; peak?: number; frames?: number; ease?: Curve; }): Move

propsFingerprint

A props object's fingerprint: what and compare to know a render would give the same element, and what a runtime's bailout can key on (Animate.fingerprint).

TS
function propsFingerprint(props: object): number

pushIn

a slow push-in on footage: scale 1 to 1 + amount (0.04) across frames (90), linear.

TS
function pushIn(o?: { amount?: number; frames?: number; }): Move

reverse

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.

TS
function reverse(m: MoveLike): Move

rise

y offset distance to 0 over frames (30 px over 6), fading in over fade (5).

TS
function rise(o?: { distance?: number; frames?: number; fade?: number; ease?: Curve; }): Move

sampleAnimation

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).

TS
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 with (x, y, opacity, scale, rotate, colours...), for parts that are animated one by one in TypeScript (a 3D text's glyphs: ). An empty object before it starts is its first key's values (an entrance holds them).

TS
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).

TS
function sequence(...steps: Step[]): Group

sequenceGap

Each step when the one before it ends, gap frames later (a negative gap overlaps them).

TS
function sequenceGap(gap: number, ...steps: Step[]): Group

slam

a hit: scale from (1.3), dip (0.96) at frames/2, 1 at frames (8); in over 2 frames.

TS
function slam(o?: { from?: number; dip?: number; frames?: number; ease?: Curve; }): Move

slide

arrives from a side: distance px (30) over frames (7), fading in over 5. from is where it comes from.

TS
function slide(o?: { from?: Dir; distance?: number; frames?: number; ease?: Curve; }): Move

stagger

The parts each frames apart, each with m. With grid, delays spread by grid distance from an origin.

TS
function stagger(parts: string[], each: number, m: MoveLike, grid?: { cols: number; rows?: number; from?: GridFrom; } | undefined): Group

strike

a strike-through line drawn: width 0 to 100% over frames (8).

TS
function strike(o?: { frames?: number; ease?: Curve; }): Move

tick

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).

TS
function tick(o: { to: number; from?: number; frames?: number; digits?: number; ease?: Curve; prefix?: string; suffix?: string; separator?: string | boolean; fade?: number; }): Move

timeline

Steps placed in time (a sequence of them). Each part's start is decided here, once.

TS
function timeline(...steps: Step[]): Plan

toMove

A name, a move or several moves together, as one move.

TS
function toMove(m: MoveLike): Move

track

A track: track({ 0: 30, 6: 0 }) is a 30 px rise over 6 frames, out-cubic by default.

TS
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.

TS
function wipe(o?: { from?: Dir; frames?: number; ease?: Curve; }): Move

Values

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

FieldTypeWhat it is
enterMoveLikethe entrance: a move's name, a move, or several together
exitMoveLikethe exit, played at exitAt (an entrance reversed: exit="rise" sinks back out)
animateRecord<string, TrackSpec | TrackSpec[] | Keys> | Movefree tracks by property, in local frames: { backgroundColor: track({ 0: "#fff", 20: "#000" }) }
atnumberlocal frame the entrance starts (0)
exitAtnumberlocal frame the exit starts
timelinePlana timeline's plan and this element's part in it: the part's move, at its start (plus at)
partstringthe 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)
asstringthe element: "div" by default
styleCSSPropertiesinline style
classNamestringTailwind or CSS classes
childrenReactNodewhat moves

AnimateProps

TS
type AnimateProps = AnimateOwnProps & { [attr: string]: unknown }

Animation

One element's declared animation.

Rust: raea_core_animate::spec::Animation.

FieldTypeWhat it is
origin (required)numberthe composition frame the element's local frame 0 falls on (where its Sequence starts)
freeze (required)number | nulla local frame to hold at whatever the composition's frame (inside a Freeze)
fps (required)numberthe 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 | nullthe local frame it's shown from, when it shows only for a window (mount="during"); before it, display none
show_until (required)number | nullthe local frame it's hidden from again

AnimGraphNode

A node of a .

TS
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.

FieldTypeWhat it is
at (required)numberthe local frame it's at
value (required)numbera number property's value there
color (required)stringa colour property's value (CSS text); empty for numbers
ease (required)stringthe curve to the next key (easing::Easing::parse); empty is linear

AnimTrack

Keyframes on one property.

Rust: raea_core_animate::spec::AnimTrack.

FieldTypeWhat it is
prop (required)stringthe 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)stringa length's unit: "px" (when empty) or "%"
repeat (required)stringpast the last key: "" or "hold", "loop", "pingpong" (for ever), or "loop:N" / "pingpong:N"
digits (required)numberdecimals a text number is written with
prefix (required)stringwritten before and after a text number (a counter's "quot; and " pts")
suffix (required)stringwritten after a text number
separator (required)stringbetween a text number's thousands ("," writes 12,345); empty: none

Curve

TS
type Curve = CurveName | (string & {})

CurveName

Curve names. Any of these, or bezier(x1,y1,x2,y2), steps(n), spring(mass,damping,stiffness), reverse(<curve>).

TS
type CurveName = | "linear" | "ease" | `${"in" | "out" | "in-out"}-${"quad" | "cubic" | "quart" | "quint" | "sine" | "expo" | "circ" | "back" | "elastic" | "bounce"}`

GridFrom

TS
type GridFrom = "top-left" | "top-right" | "bottom-left" | "bottom-right" | "center" | "top" | "bottom" | "left" | "right"

Group

FieldTypeWhat it is
kind (required)"sequence" | "parallel"sequence: one after another; parallel: together
steps (required)Step[]its steps, in order
gap (required)numberframes 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).

TS
type Keys = Record<number, V>

Move

Tracks by property, in frames from the move's own start.

FieldTypeWhat it is
tracks (required)Record<string, TrackSpec | TrackSpec[]>keyframes by property, in frames from the move's start
frames (required)numberhow long it runs (its last key)

MoveLike

TS
type MoveLike = MoveName | Move | MoveLike[]

MoveName

TS
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.

FieldTypeWhat it is
parts (required)Record<string, { at: number; move: Move }>each part's start frame and move, by its name
frames (required)numberthe whole timeline's length in frames

StaggerProps

FieldTypeWhat it is
each (required)numberframes between one child's entrance and the next
grid{ cols: number; rows?: number; from?: GridFrom }spread by grid distance instead of order
exitEachnumberthe exit staggered too, exitEach frames apart (0: all together)
styleCSSPropertiesinline style
animateRecord<string, TrackSpec | TrackSpec[] | Keys> | Movefree tracks by property, in local frames: { backgroundColor: track({ 0: "#fff", 20: "#000" }) }
asstringthe element: "div" by default
exitMoveLikethe exit, played at exitAt (an entrance reversed: exit="rise" sinks back out)
childrenReactNodewhat moves
enterMoveLikethe entrance: a move's name, a move, or several together
atnumberlocal frame the entrance starts (0)
exitAtnumberlocal 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)
classNamestringTailwind or CSS classes

Step

A timeline step: [part, move], a group from sequence/parallel/stagger, or a pause in frames.

TS
type Step = [part: string, move: MoveLike] | { part: string; move: MoveLike; offset?: number } | Group | number

TrackSpec

One property's keyframes: values by frame, the curve between each pair (one for all, or one per segment).

FieldTypeWhat 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")
digitsnumberdecimals a text counter writes
prefixstringa text counter's text before and after the number, and between its thousands ("," writes 12,345)
suffixstringwritten after a text counter's number (" pts")
separatorstringbetween a text counter's thousands ("," writes 12,345)