Animation API
Keyframes, timelines, blending, and IK — the complete animation reference.
Try it
title: "Keyframe oscillation"
description: "A shape bouncing between two positions with easing"
nodes:
- type: ELLIPSE, name: "Ball", x: 200, y: 200, w: 50, h: 50, fill: "#E74C3C"
formulas:
- node: "Ball", prop: "y", value: "200 + Math.sin(time * 3) * 100"
- node: "Ball", prop: "x", value: "200 + Math.cos(time * 2) * 80"
tools_visible: [select, play]
ui_visible: [properties, formulas]Animation clips
An animation clip is a named collection of per-node timelines.
AnimationClip {
name: string // "Walk", "Jump", "Idle"
timelines: Map<nodeId, BoneTimeline> // one timeline per animated node
}
BoneTimeline {
keyframes: FormulaKeyframe[] // sorted by time
loop: boolean // wrap time modular to last keyframe
}Each node (bone) has its own timeline with independent loop length.
Formula keyframes
Keyframes hold change data — from/to values with easing:
FormulaKeyframe {
t: number // time of keyframe
tIn: number // blend-in duration before keyframe
tOut: number // blend-out duration after keyframe
change: {
[property]: {
from: number // value at start of segment
to: number // value at end of segment
ease: EasingFunction // transition curve
}
}
}Keyframes can also hold formula expressions instead of static from/to values. The formula evaluates at each frame within the keyframe segment.
Evaluation
Sequential seek with hint
Keyframes are sorted by time. The evaluator caches _lastIndex per timeline:
- Forward playback: O(1) — advance from last known index
- Random seek: O(n) worst case, binary search fallback
Normalized time
Within a segment (between two keyframes), t is normalized 0-1:
t = (currentTime - startKeyframe.t) / (endKeyframe.t - startKeyframe.t)
The easing function transforms this:
easedT = ease(t) // e.g., easeInOut(0.5) ≈ 0.5value = from + (to - from) * easedT
Easing functions
| Name | Behavior |
|---|---|
linear | Constant speed |
easeIn | Slow start |
easeOut | Slow end |
easeInOut | Slow both ends |
cubicBezier(x1, y1, x2, y2) | Custom curve |
spring(mass, stiffness, damping) | Physics-based overshoot |
steps(count) | Discrete jumps |
elastic | Bouncy overshoot |
bounce | Landing impact |
Per-property easing — rotation bounces while position eases smoothly in the same keyframe.
Blending
Mock objects
Animations evaluate into preallocated mock objects, not directly into nodes:
AnimationMock {
x, y, rotation, width, height, opacity
}Two mocks per bone. Animation A → mockA. Animation B → mockB. Blend by weight, apply result to real node.
Blend modes
Freeze-blend: Source freezes at current pose, target advances. Weight ramps from source to target.
playAnimation("Jump", { duration: 0.3, mode: "freeze" })
Live-blend: Both animations keep playing, weight shifts gradually.
playAnimation("Run", { duration: 0.5, mode: "live", sourceSpeed: 0.5 })
State capture
Current node state is captured when playAnimation() is called. The snapshot becomes the blend source. Chained transitions (Walk → Jump → Land) capture mid-blend poses naturally.
Playback API
// Play a clip
playAnimation(clipName: string, blend?: BlendConfig)
// Stop all animation
stopAnimation()
// BlendConfig
{
duration: number // crossfade time in seconds
mode: "freeze" | "live" // blend mode
sourceSpeed?: number // during live blend, slow source
}IK solvers
IK runs after animation blending, before applying to nodes.
Two-bone (analytical)
For arms and legs. Uses law of cosines — exact answer in one frame.
CCD (Cyclic Coordinate Descent)
Iterative, any chain length. 5-15 iterations. Good for tails, tentacles.
FABRIK
Iterative, position-based. Handles constraints well. Good for spines, ropes.
IK chain definition
IKChain {
bones: string[] // nodeIds root → tip
targetSlot: string // node to reach
poleSlot?: string // bend direction hint
solver: "twoBone" | "ccd" | "fabrik"
weight: number // 0-1 blend with keyframed pose
constraints: JointConstraint[]
}
JointConstraint {
nodeId: string
minAngle: number // radians
maxAngle: number // radians
preferredAngle?: number // rest pose bias
}IK weight is animatable — keyframe it for transitions between keyframed and procedural motion.
Component slots
Slots are typed VNode inputs on components. IK targets, look-at targets, and other spatial references use slots:
// In formula: reference a slotEyeRotation = atan2($lookAtTarget.y - this.y, $lookAtTarget.x - this.x)
During authoring, slots show placeholder nodes. At runtime, they bind to real scene nodes.
Export
| Format | Supports |
|---|---|
| Standalone JS | Full: keyframes, formulas, blending, IK |
| Lottie JSON | Partial: static keyframes only (no formulas, no IK) |
| CSS @keyframes | Simple: single-property animations |
Integration with formulas
Priority order:
- Animation keyframe active → animation value
- Formula bound → formula evaluates
- Neither → static value
Formulas can reference time and frame for procedural animation. Keyframes can hold formulas. The two systems compose.
→ Formulas API → Physics API → Nodes