animate
A one-shot value tween driven by the host's frame clock: interpolates from → to over
duration and calls onUpdate with the current value every frame. It animates values, not
objects — you apply the value yourself in onUpdate. For open-ended per-frame motion use
setLoop (globals.md) instead.
At a glance
const id = animate({
from: 0,
to: 1,
duration: 400, // milliseconds
easing: easeOut,
onUpdate: v => { ghost.opacity = v },
onComplete: () => console.log('faded in'),
})Starting a tween
animate({
from: T, // start value
to: T, // end value — its shape picks the interpolation (see below)
duration: number, // MILLISECONDS
onUpdate(val: T): void, // called every frame with the interpolated value
onComplete?(): void, // called once, after the final onUpdate
easing?: Easing, // maps linear 0..1 progress; default linear
}): number // animation id for stop/pause/resumeduration is milliseconds (frame dt is seconds; UI .animateTo() durations
are also ms). See conventions.
easing receives the raw linear progress 0..1 and returns the eased progress — see
easings. onComplete fires when progress reaches 1; a tween stopped early never
fires it.
Value kinds
The shape of to selects the interpolation:
to |
Interpolation | onUpdate receives |
|---|---|---|
number |
lerp | number |
array length 2 (Vec2-like) |
per-component lerp | Float32Array (reused) |
array length 3 (Vec3-like) |
per-component lerp | Float32Array (reused) |
array length 4 (Quat-like) |
quaternion slerp | Float32Array (reused) |
for the array kinds, onUpdate receives the same backing Float32Array every
frame — it's overwritten in place. Don't store the reference; copy ([...v]) if you need to
keep a frame's value.
length-4 arrays are always treated as quaternions (slerped, with hemisphere flip) — don't tween an RGBA tuple as a length-4 array.
string values (colors). The color branch in the source is dead code (it
compares the value, not the type), so a string to falls through to the array-length checks
and produces nothing useful — NaN output for 2/3/4-character strings, a complete no-op
otherwise. No error is thrown. Animate a color by tweening a number 0..1 and mixing the two
colors yourself in onUpdate.
Controlling a running tween
stopAnimation(id: number): void // halt permanently — no further onUpdate, no onComplete
pauseAnimation(id: number): void // freeze progress
resumeAnimation(id: number): void // continue a paused tweenanimateMat4 — tweening a transform matrix
animateMat4({
from: Mat4Like, // Float32Array | number[], 16 elements
to: Mat4Like,
duration: number, // milliseconds
onUpdate(m: Float32Array): void,
onComplete?(): void,
easing?: Easing,
}): numberElement-wise matrix lerp would shear; animateMat4 instead decomposes both matrices once at
call time into position / rotation / scale, lerps position and scale, slerps rotation, and
recomposes each frame. onUpdate receives a reused 16-element Float32Array — same
don't-store-the-reference rule as the vector kinds.
Pitfalls
// ✗ duration in seconds — the tween finishes in under a frame
animate({ from: 0, to: 100, duration: 0.5, onUpdate: v => { node.x = v } })
// ✓ milliseconds
animate({ from: 0, to: 100, duration: 500, onUpdate: v => { node.x = v } })
// ✗ storing the vec value — every entry ends up the same (final) array
animate({ from: [0, 0], to: [10, 10], duration: 300, onUpdate: v => path.push(v) })
// ✓ copy the frame's value
animate({ from: [0, 0], to: [10, 10], duration: 300, onUpdate: v => path.push([...v]) })See also
- Easings —
easeIn/easeOut/easeInOut,cubicBezier, custom easings - Conventions — time — where the SDK uses seconds vs milliseconds
- Host globals —
setLoopfor open-ended per-frame animation