Anim is a purely functional and statically typed animation language for precise 2D motion, with a headless renderer for SVG, PNG, and repeatable video pipelines.
Exact time values make frame 10,000 as direct to evaluate as frame 1. Render a frame or range without replaying a mutable timeline.
Pure functions you can reason about
Typed units, immutable data, pure signals, and explicit composition boundaries turn motion into reviewable, version-controlled source.
Built for pipelines
Use the headless CLI for exact PNG and SVG output, or hand rendered frames to FFmpeg for WebM and MP4 delivery.
Early access · 0.1 previewAnim is ready to explore, but the language and CLI can still change between releases. The installer always fetches the newest preview build.
One hundred and forty points sit on a single ray. Each turns at a different multiple of the same rate, so the ray curls itself into a new rosette every few seconds — the technique John Whitney used in Catalog (1961), in fourteen lines. This player compiles the source to WebAssembly in your browser and renders it to Canvas 2D. Press Edit code, change the point count, the sweep, or a colour, and the preview recompiles as you type.
Live canvasDifferential dynamics
400 × 30060 fps
Loading renderer…
Rendered SVGDifferential dynamics
Loading rendered frames…
anim
// After John Whitney, "Catalog" (1961). One hundred and forty points sit on// a ray; each one turns at a different multiple of the same rate, so the ray// curls itself into a new rosette every few seconds and never repeats until// the half turn is done.let spin = repeat(signal: tween(from: 0deg, to: 180deg, start: 0s, end: 32s), every: 32s);let tint(i) = if i < 35 { #f0abfc } else { if i < 70 { #fbbf24 } else { if i < 105 { #34d399 } else { #38bdf8 } } };let dot(i) = rotate( angle: i * spin, scene: translate(x: 8px + i * 1.05px, y: 0px, scene: ellipse(x: -2px, y: -2px, width: 5px, height: 5px, fill: tint(i: i))));let dots = map(items: range(from: 0, to: 140), transform: dot);export let main = composition( width: 400px, height: 300px, duration: 32s, fps: 60fps, scene: stack(children: [ scene_signal(scene: rect(x: 0px, y: 0px, width: 400px, height: 300px, fill: #0b1020)), translate(x: 200px, y: 150px, scene: stack(children: dots)) ]));
The same source can render one targeted frame, a numbered image sequence, or a video through a local FFmpeg installation. Start with the ten-minute tutorial, or read the CLI and language reference.