Animation
Animation
A CindyScript framework for mathematical animation.
What It Is
Animation is a framework for producing mathematical animations in CindyJS. It manages a complete production pipeline — a live preview mode for rapid iteration, a frame exporter for high-quality video output, and a step-through mode for presentations and testing — all from a single source file with no configuration changes. It is used for the Sum and Product YouTube channel, for lecture videos, and as a substrate for building interactive mathematical demonstrations.
The framework is not a general-purpose animation tool. Its scope is deliberate and narrow: timeline management, a thin layer of visual objects, and the infrastructure to export the result as video. Everything else — geometry, algebra, rendering, interactivity — is delegated to CindyJS and CindyScript, the mathematical environment it runs on.
The Drawing Metaphor
Mathematical exposition, in its most natural form, is drawn. A line is traced across a board. A formula appears character by character. A point snaps into place. The framework takes this seriously and organises its vocabulary around the physical act of making marks visible.
ink draws a stroke or line into existence along its length. write types out a formula character by character, spacing and timing handled automatically for both plain text and KaTeX-rendered mathematics. bounceGrow pops a point or object into view with a small elastic overshoot — the weight of chalk hitting a surface. fadeIn and fadeOut manage opacity. erase reverses ink.
This is not decorative. Mathematical animations work when they follow the rhythm of explanation — when things appear exactly as fast as a viewer needs to absorb them. The drawing metaphor keeps the animator thinking in those terms rather than in terms of transitions and keyframes.
Underneath, visual objects carry their visibility as plain numbers. ink = 0 means not yet drawn; ink = 1 means fully present; anything between is an animation in progress. The rendering code reads these numbers and draws accordingly. The animation system tweens them. The two halves are completely separate, and this separation is the core of the model.
The Screenplay
The creative act of building an animation starts with a storyboard. In this framework, that storyboard is code — and ideally, it stays close to natural language:
animate([(ink, xAxis), (ladder, 0.7, ink, xTicks)], 1.2);
pause();
animate([(bounceGrow, pointA, labelA, pointB, labelB)]);
pause();
animate([(tween, numberA, "position", origin + shiftA, "easeInOutCubic")], 1.5);
pause();
Each animate()/pause() pair is a beat. Read down the list and you have the complete narrative structure of the animation — what appears, in what order, for how long. This is the heart of the package. For a well-defined animation, this list should be all you need to write.
In practice, work on a new animation begins here. Often with verbose natural-language comments describing what each beat should accomplish, which then get replaced with actual animate() calls as the structure solidifies. The goal is always to stay as close to the screenplay as possible, because the screenplay is the most readable expression of creative intent.
The timeline is linear by default. Tracks are sequential. Overlap is available when needed via negative pauses. But the animating principle — and the aesthetic ideal — is one thing after another, in the order a viewer would experience them.
Custom animations
Not everything fits into a tween or a built-in effect. update() exists for everything else.
update() runs on every frame and has full access to all track progress values via t(trackID). Camera orbits, procedurally redrawn 3D geometry, state machines, text labels that follow moving points, animations that react to the state of other animations — all of this lives in update(). It is intentionally secondary: the animate()/pause() script describes the intention; update() is the manual implementation when the built-in vocabulary runs short. In a well-designed animation, update() should be as short as possible.
render() serves a similar role — explicit control over draw order and visibility when the default rendering is insufficient.
Both exist because the framework is deliberately incomplete. Trying to absorb every possible need into the built-in command set would make the screenplay harder to write and the package harder to maintain. The boundary is pragmatic: if it can be expressed as a built-in command, it should be. If it can’t, update() is there.
Three Modes
The same file runs in three modes without modification.
REAL is the development mode. The animation plays in real time in the browser, with a debug overlay showing track progress and elapsed time. This is where iteration happens — adjust a duration, refresh, see the result immediately.
FRAMES drives a headless Puppeteer instance that steps through the animation one frame at a time, takes a screenshot at each step via Puppeteer’s native capture API, and assembles the result with ffmpeg into a 4K, 60fps MP4. Because the browser is a renderer rather than a real-time display, each frame is captured at full quality regardless of computational cost. There is no dropped-frame problem and no temporal aliasing.
STEPS converts the animation’s natural pause points into interactive waypoints. The viewer advances through them manually — like slides. Because the underlying animation is still live and parametric, interactive elements built into the scene continue to function. This makes it straightforward to build lecture material that is simultaneously a linear presentation and a live interactive demonstration, from a single file with no additional code. STEPS mode is also the fastest way to test individual animation beats during development without watching the full animation play through.
The CindyJS Foundation
The framework runs on CindyJS, a mathematical visualization environment with built-in support for interactive geometry, symbolic algebra, and real-time graphics. The choice of CindyJS is largely one of familiarity — the author has worked with it for years — but it has genuine consequences for what the framework can do.
CindyScript is a mathematical language. Vectors, matrices, and set operations are first-class. apply(-7..7, newTick(origin + [# * gridSize, 0])) is idiomatic. The math in the animation code reads like the math it describes, which matters when the animations are mathematics.
More importantly, CindyJS is inherently interactive. A finished animation can be connected to CindyJS’s event system and turned into a draggable, real-time interactive widget with very little additional work. The distance between “animation that explains a concept” and “interactive tool that lets someone explore it” is shorter here than in any video-first animation framework. STEPS mode is the most visible expression of this: presentations are not exported slides but live CindyJS applets, and the interactive layer is always available.
What This Is Not
This is not Manim. Manim is a mature, Python-based framework designed for programmers who want precise programmatic control over every visual element, with a large library of pre-built mathematical objects and a strong community of contributors. It is excellent at what it does. Animation takes a different approach: a thinner object model, a storyboard-first workflow, and deep integration with an interactive host environment. The tradeoff is expressive range for creative immediacy — and the ability to run the same file as a video, a presentation, and an interactive widget.
This is also not a tool for animators. There is no timeline GUI, no motion curves panel, no asset library. The entire interface is text. This is a deliberate choice: for mathematical content, the precision and repeatability of code is more useful than the tactile control of a GUI. The screenplay structure keeps the code readable enough that it can serve as its own documentation of what an animation does and why.
A Note on the Name
The package is currently called animation, which is accurate but not distinctive. Two alternatives worth considering: Ink, which names the central metaphor and doubles as the most-used command; or Score, which carries the dual sense of a musical score (a temporal notation system) and a screenplay, and captures the storyboard-first philosophy directly.
Built on CindyJS. Rendered with Puppeteer and ffmpeg.