New in 0.27 and 0.28
Features added in the 0.27 and 0.28 releases. Each section lists the real exported API, a copy-paste snippet, and a runnable example where the feature is visual. The MIDI and notebook sections are code samples, since both depend on the host environment.
0.27.0
GanttChart
GanttChart is a project-timeline chart. Each task is a horizontal bar that
spans its start to its end on a shared time x-axis, and each task sits on one
row. Optional dependency arrows point from a predecessor's end to a dependent
task's start, and an optional "today" marker draws a vertical line. Time values
are numbers or Date objects.
Source Code
import { GanttChart, FadeIn } from 'manim-web';
const chart = new GanttChart({
tasks: [
{ name: 'Research', start: 0, end: 3 },
{ name: 'Design', start: 3, end: 6, dependencies: ['Research'] },
{ name: 'Build', start: 6, end: 11, dependencies: ['Design'] },
{ name: 'Test', start: 9, end: 13, dependencies: ['Build'] },
{ name: 'Launch', start: 13, end: 15, dependencies: ['Test'] },
],
showDependencies: true,
today: 7, // vertical marker at t = 7
width: 9,
});
scene.play(new FadeIn(chart));
Tasks that share a row key share a row and may overlap. Options also include
rowHeight, rowGap, barColors, barFillOpacity, axisColor,
showTimeAxis, timeTicks, timeTickCount, formatTime, showLabels,
dependencyColor, and todayColor.
Kinetic typography
The kinetic-typography pack is a set of ready-made per-glyph text animations.
Each preset is a factory that returns an Animation, so
scene.play(fadeInByLetter(text)) works directly. A preset staggers an effect
across a Text's glyph submobjects. A Text has no glyph children until
loadGlyphs() builds them from a font file, so load the glyph group and animate
it; a plain Text with no glyphs animates as a single unit without throwing.
Source Code
import { Text, fadeInByLetter, waveLetters } from 'manim-web';
const text = new Text({ text: 'manim web', fontUrl: '/fonts/Inter-Regular.ttf', fontSize: 96 });
const glyphs = await text.loadGlyphs(); // a TextGlyphGroup whose children are glyphs
// Entrance: reveal the glyphs with an overlapping stagger.
await scene.play(fadeInByLetter(glyphs, { lagRatio: 0.15 }));
// Emphasis: each glyph oscillates with a per-glyph phase, then settles.
await scene.play(waveLetters(glyphs, { amplitude: 0.3, waves: 2, duration: 2 }));
Entrance presets are typeOn, fadeInByLetter, fadeInByWord, riseByLetter,
scalePopByLetter, and glitchIn. Emphasis presets are waveLetters,
tracking, and spreadOut. The underlying animation classes WaveLetters,
LetterSpacing, and GlitchIn are exported too, with resolveGlyphs(target)
for building custom presets.
MidiInput
MidiInput wraps navigator.requestMIDIAccess() so a MIDI keyboard or
controller can drive mobject properties and ValueTrackers each frame. It
mirrors AudioAnalyzer: the same feature-detection, the same bindToTracker and
createUpdater bindings, and the same degrade-to-no-op contract. In Node, jsdom,
or SSR it degrades to a no-op, so reads return zeros or null and nothing throws.
Live MIDI input needs a browser and a device, so this section is a code sample.
Source Code
import { MidiInput, isMidiSupported, ValueTracker, Dot } from 'manim-web';
if (isMidiSupported()) {
const midi = new MidiInput();
await midi.connect(); // prompts the browser for MIDI access
// Reads, each from the latest MIDI state.
midi.getNote(60); // velocity of middle C, or null if it is up
midi.isNoteOn(60); // whether middle C is held
midi.getControl(1); // mod wheel (CC 1), normalized to [0, 1]
midi.getPitchBend(); // pitch bend in [-1, 1]
// Drive a ValueTracker from a read each frame.
const level = new ValueTracker(0);
midi.bindToTracker(level, () => midi.getControl(1));
const dot = new Dot();
dot.addUpdater(() => dot.setScale(1 + level.getValue()));
// Or map MIDI onto any property with a custom updater.
dot.addUpdater(midi.createUpdater((d, m) => d.setScale(1 + m.getControl(1))));
// Event listeners each return an unsubscribe function.
const off = midi.onNoteOn((note, velocity) => console.log(note, velocity));
}
getInputNames() lists the connected ports, onNoteOff and onControlChange
add the other listeners, and dispose() detaches every handler.
Notebook embed
The manim-web/notebook subpath embeds a scene in a Jupyter or Observable cell.
embedScene creates and manages a scene in a host element (or a fresh one), and
observableScene returns a detached node for an Observable cell. The subpath
re-exports the whole library, so a cell imports mobjects and animations from the
same entry point.
Source Code
import { embedScene, observableScene, Circle, FadeIn } from 'manim-web/notebook';
// Jupyter / plain DOM: pass an element (or undefined to create one).
const { element, scene, ready, dispose } = embedScene(
undefined,
async (scene) => {
const circle = new Circle({ radius: 1 });
scene.add(circle);
await scene.play(new FadeIn(circle));
},
{ width: 640, height: 360 },
);
document.body.appendChild(element);
await ready; // resolves when the setup callback finishes
// Observable: return the node directly; invalidation disposes it on re-run.
const node = observableScene(
async (scene) => {
const dot = new Circle({ radius: 0.5 });
scene.add(dot);
await scene.play(new FadeIn(dot));
},
{ width: 800, invalidation },
);
embedScene returns { scene, element, ready, dispose }; observableScene
returns the host node with scene and dispose attached. Both take the normal
Scene options plus aspect and className.
0.28.0
NeuralNetwork
NeuralNetwork is a diagram of stacked layers with connection lines between
adjacent layers (a manim-ml analogue). It places the layers left to right and,
for every adjacent pair, draws a dense set of connection lines between their
node anchors. forwardPass then lights the connections gap by gap, from the
input layer to the output layer; backwardPass does the same in reverse.
Source Code
import { NeuralNetwork, FeedForwardLayer, forwardPass } from 'manim-web';
const net = new NeuralNetwork(
[
new FeedForwardLayer({ numNodes: 3 }),
new FeedForwardLayer({ numNodes: 5 }),
new FeedForwardLayer({ numNodes: 4 }),
new FeedForwardLayer({ numNodes: 2 }),
],
{ layerSpacing: 1.1 },
);
scene.add(net);
// Light each gap from input to output.
await scene.play(forwardPass(net, { gapDuration: 0.8 }));
Other layer types are ConvolutionalLayer, PoolingLayer, and ImageLayer.
forwardPass and backwardPass accept lagRatio, gapDuration,
activationColor, and peakOpacity; the per-line EdgePulse animation is
exported for custom flows.
ShaderMobject
ShaderMobject is a plane rendered with a user-supplied GLSL fragment shader. It
follows the Shadertoy convention with built-in uniforms updated each frame:
iTime (seconds), iResolution (the plane size), and an optional iMouse.
Construction never touches a WebGL context, so it is headless-safe; the material
builds lazily on first render. A frame updater advances iTime, so animated
shaders run on the timeline.
Source Code
import { ShaderMobject } from 'manim-web';
const shader = new ShaderMobject({
fragmentShader: `
precision highp float;
uniform float iTime;
varying vec2 vUv; // plane UV in [0, 1]
void main() {
gl_FragColor = vec4(vUv, 0.5 + 0.5 * sin(iTime), 1.0);
}
`,
width: 9,
height: 5,
uniforms: { uScale: 6.0 }, // user uniforms pass through by GLSL name
});
scene.add(shader); // the frame updater advances iTime
shader.setUniform('uScale', 8.0); // update a uniform at any time
Pass mouse: true to expose iMouse. Methods include setUniform,
getUniform, getTime, setResolution, setWidth, setHeight, and
getMaterial.
sketch
sketch(mobject, options) returns a new VMobject whose path is a perturbed,
hand-drawn version of the source (a rough.js / xkcd look). The source is never
changed. The algorithm reads the path as an anchor polyline, jitters each vertex
with a seeded PRNG, then subdivides and bows each edge. A fixed seed
reproduces the same output, and roughness: 0 reproduces the source shape.
Source Code
import { Square, Circle, VGroup, sketch, Create } from 'manim-web';
const shapes = new VGroup(
new Square({ sideLength: 2.4, color: '#58c4dd' }).shift([-2, 0, 0]),
new Circle({ radius: 1.2, color: '#ffd166' }).shift([2, 0, 0]),
);
// A hand-drawn copy; the source stays crisp.
const rough = sketch(shapes, { roughness: 1.5, bowing: 1.5, strokes: 2, seed: 7 });
scene.play(new Create(rough));
Options are roughness, bowing, strokeWidthJitter, seed, strokes (1 or
2 for a double-stroke look), and segments (straight sub-segments per edge).
Container mobjects (groups, glyph text, SVG) sketch every member that owns
points.
Physics simulation
The physics module adds a compact 2D rigid-body world. A PhysicsWorld holds
bodies and constraints and advances them with semi-implicit Euler integration.
Each body can carry a mobject, which the world moves to the body position on
every step, so adding the world to a scene renders the simulation. A fixed
seed makes the simulation deterministic. Two builders cover common setups:
pendulum (a bob pinned to a fixed pivot by a rigid distance constraint) and
springChain (a row of bodies linked by springs).
Source Code
import { pendulum, Dot, Line } from 'manim-web';
const pivot: [number, number] = [0, 2];
// A bob pinned to a fixed pivot; pass a bob mobject so it renders.
const bob = new Dot({ radius: 0.18, color: '#fc6255' });
const world = pendulum({ pivot, length: 3, angle: Math.PI / 2.2, bob });
// A rod kept in sync with the bob body each frame.
const body = world.getBody(0);
const rod = new Line({ start: [pivot[0], pivot[1], 0], end: [0, 0, 0] });
rod.addUpdater(() => rod.setEnd([body.position[0], body.position[1], 0]));
scene.add(rod);
scene.add(world); // the scene render loop ticks the simulation each frame
springChain({ count, spacing, stiffness, damping, anchored }) builds a hanging
chain. The lower-level API is new PhysicsWorld({ gravity, bounds, seed }) with
addBody, addConstraint, step(dt), reset(), and getBodies(); the
constraints are DistanceConstraint and SpringConstraint.