Animations
Interactive 2D animation examples built with manim-web.
Integrated Player
A full-featured playback controller with play/pause, segment navigation, timeline scrubbing, speed control, fullscreen, and export. Use Space to play/pause, ←/→ for segments, Shift+←/→ to scrub, and F for fullscreen.
Source Code
import {
Player,
Circle,
Square,
Triangle,
Create,
Transform,
FadeIn,
FadeOut,
Indicate,
Rotate,
BLACK,
BLUE,
RED,
GREEN,
YELLOW,
} from 'manim-web';
const player = new Player(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
player.sequence(async (scene) => {
// Create a blue circle
const circle = new Circle({ radius: 1.5, color: BLUE });
await scene.play(new Create(circle));
await scene.wait(0.5);
// Transform to red square
const square = new Square({ sideLength: 3, color: RED });
await scene.play(new Transform(circle, square));
// Indicate
await scene.play(new Indicate(circle));
// Transform to green triangle
const triangle = new Triangle({ color: GREEN });
triangle.scale(2);
await scene.play(new Transform(circle, triangle));
// Rotate 180°
await scene.play(new Rotate(circle, { angle: Math.PI }));
// Fade out
await scene.play(new FadeOut(circle));
// Bring in a yellow circle
const circle2 = new Circle({ radius: 1, color: YELLOW });
await scene.play(new FadeIn(circle2));
await scene.wait(1);
});
Learn More: Player · Circle · Square · Triangle · Create · Transform · Indicate · Rotate
Opening Manim
A multi-part showcase: writes text and a LaTeX equation, transforms the title, creates a NumberPlane grid, and applies a non-linear sine warp using ApplyPointwiseFunction.
Source Code
import {
Scene,
Create,
FadeIn,
FadeOut,
Transform,
ApplyPointwiseFunction,
Text,
MathTex,
NumberPlane,
VGroup,
UP,
DOWN,
UL,
BLACK,
WHITE,
} from 'manim-web';
const FONT_URL = 'https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/fonts/KaTeX_Main-Regular.ttf';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
// Part 1: Title and equation (Write title, FadeIn equation from below)
const title = new Text({
text: 'This is some LaTeX',
fontSize: 48,
color: WHITE,
fontUrl: FONT_URL,
});
const basel = new MathTex({ latex: '\\sum_{n=1}^\\infty \\frac{1}{n^2} = \\frac{\\pi^2}{6}' });
await basel.waitForRender?.();
new VGroup(title, basel).arrange(DOWN);
scene.add(title, basel);
await scene.play(new FadeIn(title), new FadeIn(basel, { shift: DOWN }));
await scene.wait(1);
// Part 2: Transform title to UL corner, fade out equation downward
const transformTitle = new Text({
text: 'That was a transform',
fontSize: 48,
color: WHITE,
fontUrl: FONT_URL,
});
await transformTitle.loadGlyphs();
transformTitle.toCorner(UL);
await scene.play(new Transform(title, transformTitle), new FadeOut(basel, { shift: DOWN }));
await scene.wait(1);
// Part 3: Number plane grid with title
const grid = new NumberPlane();
const gridTitle = new Text({
text: 'This is a grid',
fontSize: 72,
color: WHITE,
fontUrl: FONT_URL,
});
await gridTitle.loadGlyphs();
gridTitle.moveTo(transformTitle);
await scene.play(
new FadeOut(title),
new FadeIn(gridTitle, { shift: UP }),
new Create(grid, { duration: 3, lagRatio: 0.1 }),
);
await scene.wait(1);
// Part 4: Non-linear grid transform (sin warp)
const gridTransformTitle = new Text({
text: 'That was a non-linear function\napplied to the grid',
fontSize: 48,
color: WHITE,
fontUrl: FONT_URL,
});
await gridTransformTitle.loadGlyphs();
gridTransformTitle.moveTo(gridTitle, UL);
grid.prepareForNonlinearTransform();
await scene.play(
new ApplyPointwiseFunction(
grid,
(p) => {
return [p[0] + Math.sin(p[1]), p[1] + Math.sin(p[0]), p[2]];
},
{ duration: 3 },
),
);
await scene.wait(1);
// Part 5: Transform grid title to explain what happened
await scene.play(new Transform(gridTitle, gridTransformTitle));
await scene.wait(1);
Learn More: Text · MathTex · NumberPlane · Write · Transform · ApplyPointwiseFunction · Create
Manim Ce Logo
Creates the Manim Community Edition logo using a large blackboard-bold M, a circle, square, and triangle in signature colors, all composed into a VGroup.
Source Code
import {
Circle,
LEFT,
MathTex,
ORIGIN,
RIGHT,
Scene,
Square,
Triangle,
UP,
VGroup,
addVec,
scaleVec,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: '#ece6e2',
});
const logoGreen = '#87c2a5';
const logoBlue = '#525893';
const logoRed = '#e07a5f';
const logoBlack = '#343434';
const dsM = new MathTex({ latex: '\\mathbb{M}', fillColor: logoBlack });
await dsM.waitForRender();
dsM.scale(7);
dsM.shift(addVec(scaleVec(2.25, LEFT), scaleVec(1.5, UP)));
const circle = new Circle({ color: logoGreen, fillOpacity: 1 }).shift(LEFT);
const square = new Square({ color: logoBlue, fillOpacity: 1 }).shift(UP);
const triangle = new Triangle({ color: logoRed, fillOpacity: 1 }).shift(RIGHT);
const logo = new VGroup(triangle, square, circle, dsM);
logo.moveTo(ORIGIN);
scene.add(logo);
Learn More: MathTex · Circle · Square · Triangle · VGroup
Brace Annotation
Shows how to annotate lines with curly braces and labels. Creates a diagonal line between two dots, then adds horizontal and perpendicular braces with text and LaTeX labels.
Source Code
import { Brace, Dot, Line, ORANGE, Scene } from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: '#000000',
});
const dot = new Dot({ point: [-2, -1, 0] });
const dot2 = new Dot({ point: [2, 1, 0] });
const line = new Line({ start: dot.getCenter(), end: dot2.getCenter() }).setColor(ORANGE);
const b1 = new Brace(line);
const b1text = b1.getText('Horizontal distance');
const b2 = new Brace(line, {
direction: line
.copy()
.rotate(Math.PI / 2)
.getUnitVector(),
});
const b2text = b2.getTex('x-x_1');
scene.add(line, dot, dot2, b1, b2, b1text, b2text);
Learn More: Brace · Dot · Line
Vector Arrow
Displays a vector arrow on a coordinate plane with labeled origin and tip points. Demonstrates combining NumberPlane, Arrow, Dot, and Text for basic vector visualization.
Source Code
import { Arrow, DOWN, Dot, NumberPlane, ORIGIN, RIGHT, Scene, Text } from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: '#000000',
});
const dot = new Dot({ point: ORIGIN });
const arrow = new Arrow({ start: ORIGIN, end: [2, 2, 0] });
const numberplane = new NumberPlane();
const originText = new Text({ text: '(0, 0)' }).nextTo(dot, DOWN);
const tipText = new Text({ text: '(2, 2)' }).nextTo(arrow.getEnd(), RIGHT);
scene.add(numberplane, dot, arrow, originText, tipText);
Learn More: Arrow · NumberPlane · Dot · Text
Boolean Operations
Demonstrates the four boolean set operations (union, intersection, difference, exclusion) applied to overlapping ellipses. Each result is scaled down and labeled with animated transitions.
Source Code
import {
BLUE,
Difference,
DOWN,
Ellipse,
Exclusion,
FadeIn,
GREEN,
Group,
Intersection,
LEFT,
MoveToTarget,
ORANGE,
PINK,
RED,
RIGHT,
Scene,
Text,
Underline,
UP,
Union,
WHITE,
YELLOW,
scaleVec,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: '#000000',
});
const ellipse1 = new Ellipse({
width: 4.0,
height: 5.0,
fillOpacity: 0.5,
color: BLUE,
strokeWidth: 2,
}).moveTo(LEFT);
const ellipse2 = ellipse1.copy().setColor(RED).moveTo(RIGHT);
// Large italic underlined title matching Python Manim reference
const bool_ops_text = new Text({
text: 'Boolean Operation',
fontFamily: 'serif',
fontSize: 48,
}).nextTo(ellipse1, UP);
const ellipse_group = new Group(bool_ops_text, ellipse1, ellipse2).moveTo(scaleVec(3, LEFT));
// Create underline AFTER group is positioned so it uses the text's final position
const underline = new Underline(bool_ops_text, { color: WHITE, strokeWidth: 2, buff: -0.25 });
ellipse_group.add(underline);
await scene.play(new FadeIn(ellipse_group));
// Layout matching Python Manim reference:
// Intersection
// Difference Union
// Exclusion
// Intersection, Union, Exclusion are vertically aligned on the right.
// Difference is offset to the left at the same row as Union.
// Positions and scales matching Python Manim reference exactly:
// Intersection: scale(0.25), move_to(RIGHT*5 + UP*2.5)
// Union: scale(0.3), next_to(i, DOWN, buff=text.height*3)
// Exclusion: scale(0.3), next_to(u, DOWN, buff=text.height*3.5)
// Difference: scale(0.3), next_to(u, LEFT, buff=text.height*3.5)
const rightX = 5;
const i = new Intersection(ellipse1, ellipse2, { color: GREEN, fillOpacity: 0.5 });
i.generateTarget();
i.targetCopy.scale(0.25).setStrokeWidth(1).moveTo([rightX, 2.5, 0]);
await scene.play(new MoveToTarget(i));
const intersection_text = new Text({ text: 'Intersection', fontSize: 23 }).nextTo(i, UP);
await scene.play(new FadeIn(intersection_text));
const u = new Union(ellipse1, ellipse2, { color: ORANGE, fillOpacity: 0.5 });
const union_text = new Text({ text: 'Union', fontSize: 23 });
u.generateTarget();
u.targetCopy
.scale(0.3)
.setStrokeWidth(1.2)
.nextTo(i, DOWN, union_text.getHeight() * 3);
await scene.play(new MoveToTarget(u));
union_text.nextTo(u, UP);
await scene.play(new FadeIn(union_text));
const e = new Exclusion(ellipse1, ellipse2, { color: YELLOW, fillOpacity: 0.5 });
const exclusion_text = new Text({ text: 'Exclusion', fontSize: 23 });
e.generateTarget();
e.targetCopy
.scale(0.3)
.setStrokeWidth(1.2)
.nextTo(u, DOWN, exclusion_text.getHeight() * 3.5);
await scene.play(new MoveToTarget(e));
exclusion_text.nextTo(e, UP);
await scene.play(new FadeIn(exclusion_text));
const d = new Difference(ellipse1, ellipse2, { color: PINK, fillOpacity: 0.5 });
const difference_text = new Text({ text: 'Difference', fontSize: 23 });
d.generateTarget();
d.targetCopy
.scale(0.3)
.setStrokeWidth(1.2)
.nextTo(u, LEFT, difference_text.getHeight() * 3.5);
await scene.play(new MoveToTarget(d));
difference_text.nextTo(d, UP);
await scene.play(new FadeIn(difference_text));
Learn More: Union · Intersection · Difference · Exclusion · Ellipse · FadeIn · MoveToTarget
Mathtex Svg
Demonstrates vector-based LaTeX rendering with MathTexSVG. Shows Create (stroke-draw reveal), DrawBorderThenFill, FadeIn, and multi-part expressions with per-part coloring. Unlike raster MathTex, MathTexSVG produces real VMobject paths that support path-based animations.
Source Code
import {
Scene,
MathTexSVG,
Create,
DrawBorderThenFill,
FadeIn,
FadeOut,
BLACK,
WHITE,
RED,
BLUE,
GREEN,
YELLOW,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
// Pre-create all equations
const equation1 = new MathTexSVG({
latex: '\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}',
color: WHITE,
fontSize: 2,
});
const equation2 = new MathTexSVG({
latex: 'e^{i\\pi} + 1 = 0',
color: YELLOW,
fontSize: 2.5,
});
const multiPart = new MathTexSVG({
latex: ['E', '=', 'mc^2'],
color: WHITE,
fontSize: 3,
});
const equation3 = new MathTexSVG({
latex: '\\sum_{k=1}^{n} k = \\frac{n(n+1)}{2}',
color: GREEN,
fontSize: 2,
});
const matrix = new MathTexSVG({
latex: 'A = \\begin{pmatrix} a_{11} & a_{12} \\\\ a_{21} & a_{22} \\end{pmatrix}',
color: WHITE,
fontSize: 2,
});
// Render all SVGs in parallel
await Promise.all([
equation1.waitForRender(),
equation2.waitForRender(),
multiPart.waitForRender(),
equation3.waitForRender(),
matrix.waitForRender(),
]);
// 1. Create animation - stroke-draw reveal (the main feature)
await scene.play(new Create(equation1, { duration: 3 }));
await scene.wait(1);
await scene.play(new FadeOut(equation1));
// 2. DrawBorderThenFill animation
await scene.play(new DrawBorderThenFill(equation2, { duration: 2 }));
await scene.wait(1);
await scene.play(new FadeOut(equation2));
// 3. Multi-part with per-part coloring
multiPart.getPart(0).setColor(RED);
multiPart.getPart(1).setColor(WHITE);
multiPart.getPart(2).setColor(BLUE);
await scene.play(new FadeIn(multiPart));
await scene.wait(2);
await scene.play(new FadeOut(multiPart));
// 4. Another Create with a summation
await scene.play(new Create(equation3, { duration: 2 }));
await scene.wait(2);
await scene.play(new FadeOut(equation3));
// 5. 2x2 matrix with subscript indices
await scene.play(new Create(matrix, { duration: 2 }));
await scene.wait(2);
Learn More: MathTexSVG · Create · DrawBorderThenFill · FadeIn · FadeOut
Point Moving On Shapes
Grows a circle from its center, transforms a dot to a new position, moves it along the circle path with MoveAlongPath, and rotates it around an external point with Rotating.
Source Code
import {
BLUE,
Circle,
Dot,
GrowFromCenter,
Line,
MoveAlongPath,
RIGHT,
Rotating,
Scene,
Transform,
linear,
BLACK,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
const circle = new Circle({ radius: 1, color: BLUE });
const dot = new Dot();
const dot2 = dot.copy().shift(RIGHT);
scene.add(dot);
const line = new Line({ start: [3, 0, 0], end: [5, 0, 0] });
scene.add(line);
await scene.play(new GrowFromCenter(circle));
await scene.play(new Transform(dot, dot2));
await scene.play(new MoveAlongPath(dot, { path: circle, duration: 2, rateFunc: linear }));
await scene.play(new Rotating(dot, { aboutPoint: [2, 0, 0], duration: 1.5 }));
await scene.wait();
Learn More: Circle · Dot · GrowFromCenter · Transform · MoveAlongPath · Rotating
Moving Around
Demonstrates the MoveToTarget pattern for animating a square through a sequence of transformations: shifting, changing fill color, scaling, and rotating.
Source Code
import {
Scene,
Square,
Shift,
MoveToTarget,
Scale,
Rotate,
BLUE,
ORANGE,
LEFT,
BLACK,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
const square = new Square({ color: BLUE, fillOpacity: 1 });
await scene.play(new Shift(square, { direction: LEFT }));
square.generateTarget();
square.targetCopy.setFill(ORANGE);
await scene.play(new MoveToTarget(square));
await scene.play(new Scale(square, { scaleFactor: 0.3 }));
await scene.play(new Rotate(square, { angle: 0.4 }));
Learn More: Square · MoveToTarget
Moving Angle
Creates two lines forming an angle with a LaTeX theta label, then animates the angle changing using a ValueTracker. The angle arc and label update reactively via addUpdater.
Source Code
import {
Angle,
FadeToColor,
LEFT,
Line,
MathTex,
RED,
RIGHT,
SMALL_BUFF,
Scene,
ValueTracker,
BLACK,
WHITE,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
const rotation_center = LEFT;
const theta_tracker = new ValueTracker(110);
const line1 = new Line({ start: LEFT, end: RIGHT });
const line_moving = new Line({ start: LEFT, end: RIGHT });
const line_ref = line_moving.copy();
line_moving.rotate(theta_tracker.getValue() * (Math.PI / 180), { aboutPoint: rotation_center });
const a = new Angle({ line1: line1, line2: line_moving }, { radius: 0.5, otherAngle: false });
const tex = new MathTex({ latex: '\\theta', color: WHITE });
await tex.waitForRender();
tex.moveTo(
new Angle(
{ line1: line1, line2: line_moving },
{ radius: 0.5 + 3 * SMALL_BUFF, otherAngle: false },
).pointFromProportion(0.5),
);
scene.add(line1, line_moving, a, tex);
await scene.wait(1);
line_moving.addUpdater((x) => {
x.become(line_ref.copy());
x.rotate(theta_tracker.getValue() * (Math.PI / 180), { aboutPoint: rotation_center });
});
a.addUpdater((x) =>
x.become(new Angle({ line1: line1, line2: line_moving }, { radius: 0.5, otherAngle: false })),
);
tex.addUpdater((x) =>
x.moveTo(
new Angle(
{ line1: line1, line2: line_moving },
{ radius: 0.5 + 3 * SMALL_BUFF, otherAngle: false },
).pointFromProportion(0.5),
),
);
await scene.play(theta_tracker.animateTo(40));
await scene.play(theta_tracker.animateTo(theta_tracker.getValue() + 140));
await scene.play(new FadeToColor(tex, { color: RED, duration: 0.5 }));
await scene.play(theta_tracker.animateTo(350));
await scene.wait(1);
Learn More: Angle · Line · MathTex · ValueTracker · FadeToColor
Moving Dots
Creates two dots connected by a line, then animates them independently using ValueTrackers. The connecting line updates reactively via addUpdater and the become() method.
Source Code
import { Scene, Dot, VGroup, Line, ValueTracker, BLUE, GREEN, RED, RIGHT } from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: '#000000',
});
const d1 = new Dot({ color: BLUE });
const d2 = new Dot({ color: GREEN });
new VGroup(d1, d2).arrange(RIGHT, 1);
const l1 = new Line({ start: d1.getCenter(), end: d2.getCenter() }).setColor(RED);
const x = new ValueTracker(0);
const y = new ValueTracker(0);
d1.addUpdater((z) => z.setX(x.getValue()));
d2.addUpdater((z) => z.setY(y.getValue()));
l1.addUpdater((z) => z.become(new Line({ start: d1.getCenter(), end: d2.getCenter() })));
scene.add(d1, d2, l1);
await scene.play(x.animateTo(5));
await scene.play(y.animateTo(4));
await scene.wait();
Learn More: Dot · Line · VGroup · ValueTracker
Moving Group To Destination
Arranges a group of dots in a row, then shifts the entire group so a specific dot aligns with a target position. Shows vector math with subVec for computing shift direction.
Source Code
import {
Scene,
VGroup,
Dot,
Shift,
LEFT,
ORIGIN,
RIGHT,
RED,
YELLOW,
BLACK,
scaleVec,
subVec,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
const group = new VGroup(
new Dot({ point: LEFT }),
new Dot({ point: ORIGIN }),
new Dot({ point: RIGHT, color: RED }),
new Dot({ point: scaleVec(2, RIGHT) }),
).scale(1.4);
const dest = new Dot({ point: [4, 3, 0], color: YELLOW });
scene.add(group, dest);
await scene.play(
new Shift(group, {
direction: subVec(dest.getCenter(), group.get(2).getCenter()),
}),
);
await scene.wait(0.5);
Learn More: VGroup · Dot · Shift
Moving Frame Box
Renders a multi-part LaTeX equation (the product rule), then highlights individual terms with a SurroundingRectangle that animates between terms using ReplacementTransform.
Source Code
import {
Create,
MathTex,
ReplacementTransform,
Scene,
SurroundingRectangle,
Write,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: '#000000',
});
const text = new MathTex({
latex: ['\\frac{d}{dx}f(x)g(x)=', 'f(x)\\frac{d}{dx}g(x)', '+', 'g(x)\\frac{d}{dx}f(x)'],
});
await text.waitForRender();
await scene.play(new Write(text));
const framebox1 = new SurroundingRectangle(text.getPart(1), { buff: 0.1 });
const framebox2 = new SurroundingRectangle(text.getPart(3), { buff: 0.1 });
await scene.play(new Create(framebox1));
await scene.wait();
await scene.play(new ReplacementTransform(framebox1, framebox2));
await scene.wait();
Learn More: MathTex · SurroundingRectangle · Create · ReplacementTransform
Rotation Updater
Shows a reference line alongside a rotating line driven by a time-based updater function. The updater is swapped mid-animation to reverse the rotation direction.
Source Code
import { Scene, Line, ORIGIN, LEFT, WHITE, YELLOW } from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: '#000000',
});
const updaterForth = (mobj, dt) => {
mobj.rotateAboutOrigin(dt);
};
const updaterBack = (mobj, dt) => {
mobj.rotateAboutOrigin(-dt);
};
const lineReference = new Line({ start: ORIGIN, end: LEFT }).setColor(WHITE);
const lineMoving = new Line({ start: ORIGIN, end: LEFT }).setColor(YELLOW);
lineMoving.addUpdater(updaterForth);
scene.add(lineReference, lineMoving);
await scene.wait(2);
lineMoving.removeUpdater(updaterForth);
lineMoving.addUpdater(updaterBack);
await scene.wait(2);
lineMoving.removeUpdater(updaterBack);
await scene.wait(0.5);
Learn More: Line
Point With Trace
Creates a dot that leaves a visible trail as it moves. Uses a VMobject with addUpdater to continuously extend the path, then rotates and shifts the dot to draw a pattern.
Source Code
import { Scene, VMobject, Dot, Rotating, Shift, UP, LEFT, RIGHT, BLACK } from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
const path = new VMobject();
path.fillOpacity = 0;
const dot = new Dot();
path.setPointsAsCorners([dot.getCenter(), dot.getCenter()]);
const updatePath = (pathMob) => {
const previousPath = pathMob.copy();
previousPath.addPointsAsCorners([dot.getCenter()]);
pathMob.become(previousPath);
};
path.addUpdater(updatePath);
scene.add(path, dot);
await scene.play(new Rotating(dot, { angle: Math.PI, aboutPoint: RIGHT, duration: 2 }));
await scene.wait();
await scene.play(new Shift(dot, { direction: UP }));
await scene.play(new Shift(dot, { direction: LEFT }));
await scene.wait();
Learn More: VMobject · Dot · Rotating · Shift
Sine Curve Unit Circle
Animates a dot orbiting a unit circle while tracing the corresponding sine curve. Uses addUpdater for continuous motion, with connecting lines from origin-to-dot and dot-to-curve updating each frame.
Source Code
import {
Scene,
Circle,
Dot,
Line,
VGroup,
MathTex,
BLACK,
BLUE,
RED,
YELLOW,
YELLOW_A,
YELLOW_D,
DOWN,
} from 'manim-web';
const TAU = 2 * Math.PI;
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
// --- Axes ---
const xAxis = new Line({ start: [-6, 0, 0], end: [6, 0, 0] });
const yAxis = new Line({ start: [-4, -2, 0], end: [-4, 2, 0] });
scene.add(xAxis, yAxis);
// --- X labels ---
const xLabels = [
new MathTex({ latex: '\\pi' }),
new MathTex({ latex: '2\\pi' }),
new MathTex({ latex: '3\\pi' }),
new MathTex({ latex: '4\\pi' }),
];
for (let i = 0; i < xLabels.length; i++) {
xLabels[i].nextTo([-1 + 2 * i, 0, 0], DOWN, 0.4);
scene.add(xLabels[i]);
}
const originPoint = [-4, 0, 0];
const curveStart = [-3, 0, 0];
// --- Circle ---
const circle = new Circle({ radius: 1, center: originPoint, color: RED });
scene.add(circle);
// --- Dot orbiting the circle ---
let tOffset = 0;
const rate = 0.25;
const dot = new Dot({
radius: 0.08,
color: YELLOW,
point: circle.pointAtAngle(0),
});
const goAroundCircle = (_mob, dt) => {
tOffset += dt * rate;
dot.moveTo(circle.pointAtAngle((tOffset % 1) * TAU));
};
dot.addUpdater(goAroundCircle);
// --- Line from origin to dot (always_redraw equivalent) ---
const originToCircleLine = new Line({
start: originPoint,
end: dot.getPoint(),
color: BLUE,
});
originToCircleLine.addUpdater(() => {
originToCircleLine.setStart(originPoint);
originToCircleLine.setEnd(dot.getPoint());
});
// --- Line from dot to curve (always_redraw equivalent) ---
const dotToCurveLine = new Line({
start: dot.getPoint(),
end: dot.getPoint(),
color: YELLOW_A,
strokeWidth: 2,
});
dotToCurveLine.addUpdater(() => {
const x = curveStart[0] + tOffset * 4;
const y = dot.getPoint()[1];
dotToCurveLine.setStart(dot.getPoint());
dotToCurveLine.setEnd([x, y, 0]);
});
// --- Growing sine curve ---
const curve = new VGroup();
curve.add(new Line({ start: curveStart, end: curveStart, color: YELLOW_D }));
let lastEnd = [...curveStart];
curve.addUpdater(() => {
const x = curveStart[0] + tOffset * 4;
const y = dot.getPoint()[1];
const newLine = new Line({
start: lastEnd,
end: [x, y, 0],
color: YELLOW_D,
});
curve.add(newLine);
lastEnd = [x, y, 0];
});
// Add in order: dot first so tOffset/position updates before lines read it
scene.add(dot, originToCircleLine, dotToCurveLine, curve);
await scene.wait(8.5);
dot.removeUpdater(goAroundCircle);
Learn More: Circle · Dot · Line · VGroup · MathTex
Apply Matrix Arrows
Shows how ApplyMatrix works on Arrows and a NumberPlane. A shear transformation is applied to multiple arrows, and their tips are automatically reconstructed so they remain properly shaped.
Source Code
import {
Arrow,
NumberPlane,
Scene,
Text,
YELLOW,
GREEN_C,
RED_C,
applyMatrix,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: '#000000',
});
const plane = new NumberPlane();
scene.add(plane);
const arrow1 = new Arrow({ start: [-2, -1, 0], end: [2, 1, 0], color: YELLOW });
const arrow2 = new Arrow({ start: [0, -2, 0], end: [0, 2, 0], color: GREEN_C });
const arrow3 = new Arrow({ start: [-1, 1, 0], end: [1, -1, 0], color: RED_C });
scene.add(arrow1, arrow2, arrow3);
const label = new Text({ text: 'Before shear', fontSize: 24, color: '#ffffff' });
label.moveTo([0, 3.2, 0]);
scene.add(label);
await scene.wait(1);
// Shear matrix: x' = x + 0.5*y, y' = y
const shearMatrix = [
[1, 0.5, 0],
[0, 1, 0],
[0, 0, 1],
];
// Apply the shear to the plane and arrows simultaneously
await scene.play(
applyMatrix(plane, shearMatrix, { duration: 2 }),
applyMatrix(arrow1, shearMatrix, { duration: 2 }),
applyMatrix(arrow2, shearMatrix, { duration: 2 }),
applyMatrix(arrow3, shearMatrix, { duration: 2 }),
);
// Update label
scene.remove(label);
const label2 = new Text({
text: 'After shear — tips reconstructed',
fontSize: 24,
color: '#ffffff',
});
label2.moveTo([0, 3.2, 0]);
scene.add(label2);
await scene.wait(2);
Learn More: Arrow · NumberPlane · ApplyMatrix
Apply Complex Function
Applies a complex function to a NumberPlane, treating each point's (x, y) coordinates as a complex number z = x + iy. Here, the function z => z^2 bends the grid into parabolic curves, visualizing how complex squaring maps the plane.
Source Code
import {
Scene,
NumberPlane,
Create,
ApplyComplexFunction,
BLACK,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
// Create a number plane grid
const grid = new NumberPlane();
grid.prepareForNonlinearTransform();
scene.add(grid);
await scene.play(new Create(grid, { duration: 2, lagRatio: 0.1 }));
await scene.wait(0.5);
// Apply z => z^2: squares every complex number
// This maps the grid non-linearly, bending lines into parabolic curves
await scene.play(
new ApplyComplexFunction(grid, {
func: (z) => ({
re: z.re * z.re - z.im * z.im,
im: 2 * z.re * z.im,
}),
duration: 3,
}),
);
await scene.wait(1);
Learn More: ApplyComplexFunction · NumberPlane · Create
Apply Pointwise Function To Center
Applies a pointwise function relative to each mobject's center rather than absolute coordinates. Here, three shapes at different positions are each scaled by 1.5x around their own center -- they grow in place without drifting toward or away from the origin.
Source Code
import {
Scene,
Circle,
Square,
Triangle,
FadeIn,
ApplyPointwiseFunctionToCenter,
BLUE,
GREEN_C,
RED_C,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: '#000000',
});
// Create three shapes at different positions
const circle = new Circle({ radius: 0.8, color: BLUE });
circle.moveTo([-3, 0, 0]);
const square = new Square({ sideLength: 1.4, color: GREEN_C });
square.moveTo([0, 0, 0]);
const triangle = new Triangle({ color: RED_C });
triangle.scale(0.8);
triangle.moveTo([3, 0, 0]);
scene.add(circle, square, triangle);
await scene.play(
new FadeIn(circle, { duration: 0.8 }),
new FadeIn(square, { duration: 0.8 }),
new FadeIn(triangle, { duration: 0.8 }),
);
await scene.wait(0.5);
// Scale each shape by 1.5x around its own center
const scaleFn = (p: number[]) => [p[0] * 1.5, p[1] * 1.5, p[2]];
await scene.play(
new ApplyPointwiseFunctionToCenter(circle, scaleFn, { duration: 2 }),
new ApplyPointwiseFunctionToCenter(square, scaleFn, { duration: 2 }),
new ApplyPointwiseFunctionToCenter(triangle, scaleFn, { duration: 2 }),
);
await scene.wait(1);
Learn More: ApplyPointwiseFunctionToCenter · Circle · Square
Rate Functions Comparison
Example demonstrating Rate Functions Comparison.
Source Code
import {
Scene,
Dot,
Text,
Line,
Shift,
AnimationGroup,
RIGHT,
BLACK,
BLUE,
RED,
GREEN,
YELLOW,
PURPLE,
ORANGE,
WHITE,
smooth,
runningStart,
thereAndBackWithPause,
lingering,
exponentialDecay,
slowInto,
scaleVec,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
// Rate functions to compare, each with a label and color
const rateFunctions: Array<{
name: string;
rateFunc: (t: number) => number;
color: string;
}> = [
{ name: 'smooth', rateFunc: smooth, color: BLUE },
{ name: 'runningStart', rateFunc: runningStart(-0.2), color: RED },
{ name: 'thereAndBackWithPause', rateFunc: thereAndBackWithPause(), color: GREEN },
{ name: 'lingering', rateFunc: lingering, color: YELLOW },
{ name: 'exponentialDecay', rateFunc: exponentialDecay(), color: PURPLE },
{ name: 'slowInto', rateFunc: slowInto, color: ORANGE },
];
const ROW_COUNT = rateFunctions.length;
const TOP_Y = 2.0;
const ROW_SPACING = 0.7;
const START_X = -2.5;
const SHIFT_DISTANCE = 5.0;
const dots: Dot[] = [];
const shiftDirection = scaleVec(SHIFT_DISTANCE, RIGHT) as [number, number, number];
for (let i = 0; i < ROW_COUNT; i++) {
const y = TOP_Y - i * ROW_SPACING;
const { name, color } = rateFunctions[i];
// Label on the left
const label = new Text({
text: name,
fontSize: 20,
color: WHITE,
});
label.moveTo([START_X - 2.0, y, 0]);
// Track line (faint guide)
const trackLine = new Line({
start: [START_X, y, 0],
end: [START_X + SHIFT_DISTANCE, y, 0],
color: '#333333',
strokeWidth: 1,
});
// Dot at the start position
const dot = new Dot({
point: [START_X, y, 0],
radius: 0.1,
color,
});
scene.add(label, trackLine, dot);
dots.push(dot);
}
// Build simultaneous shift animations with different rate functions
const animations = dots.map(
(dot, i) =>
new Shift(dot, {
direction: shiftDirection,
duration: 3,
rateFunc: rateFunctions[i].rateFunc,
}),
);
await scene.play(new AnimationGroup(animations));
Easing Functions Showcase
Compares eight easing functions side by side. Colored dots shift right simultaneously using different rate functions — smooth, sine, back, elastic, bounce, circ, smoothstep, and exponential — to visualize how each one affects animation timing.
Source Code
import {
Scene,
Dot,
Text,
Line,
Shift,
AnimationGroup,
RIGHT,
BLACK,
BLUE,
RED,
GREEN,
YELLOW,
PURPLE,
ORANGE,
WHITE,
smooth,
easeInOutSine,
easeInOutBack,
easeOutElastic,
easeOutBounce,
easeInOutCirc,
smoothstep,
easeInOutExpo,
scaleVec,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
// Rate functions to compare, each with a label and color
const rateFunctions: Array<{
name: string;
rateFunc: (t: number) => number;
color: string;
}> = [
{ name: 'smooth', rateFunc: smooth, color: BLUE },
{ name: 'easeInOutSine', rateFunc: easeInOutSine, color: RED },
{ name: 'easeInOutBack', rateFunc: easeInOutBack, color: GREEN },
{ name: 'easeOutElastic', rateFunc: easeOutElastic, color: YELLOW },
{ name: 'easeOutBounce', rateFunc: easeOutBounce, color: PURPLE },
{ name: 'easeInOutCirc', rateFunc: easeInOutCirc, color: ORANGE },
{ name: 'smoothstep', rateFunc: smoothstep, color: '#ff69b4' },
{ name: 'easeInOutExpo', rateFunc: easeInOutExpo, color: '#00ced1' },
];
const ROW_COUNT = rateFunctions.length;
const TOP_Y = 2.5;
const ROW_SPACING = 0.65;
const START_X = -2.2;
const SHIFT_DISTANCE = 5.0;
const dots: Dot[] = [];
const shiftDirection = scaleVec(SHIFT_DISTANCE, RIGHT) as [number, number, number];
for (let i = 0; i < ROW_COUNT; i++) {
const y = TOP_Y - i * ROW_SPACING;
const { name, color } = rateFunctions[i];
// Label on the left
const label = new Text({
text: name,
fontSize: 18,
color: WHITE,
});
label.moveTo([START_X - 2.3, y, 0]);
// Track line (faint guide)
const trackLine = new Line({
start: [START_X, y, 0],
end: [START_X + SHIFT_DISTANCE, y, 0],
color: '#333333',
strokeWidth: 1,
});
// Dot at the start position
const dot = new Dot({
point: [START_X, y, 0],
radius: 0.1,
color,
});
scene.add(label, trackLine, dot);
dots.push(dot);
}
// Build simultaneous shift animations with different rate functions
const animations = dots.map(
(dot, i) =>
new Shift(dot, {
direction: shiftDirection,
duration: 3,
rateFunc: rateFunctions[i].rateFunc,
}),
);
await scene.play(new AnimationGroup(animations));
Learn More: Shift · AnimationGroup
Broadcast
Animates a mobject emanating outward from a focal point with multiple fading copies. Each copy starts small and transparent, scaling up to the original size while fading out. Supports custom focal point, number of copies, opacity range, and stagger timing.
Source Code
import {
Scene,
Circle,
Square,
Broadcast,
Create,
FadeIn,
FadeOut,
TEAL_A,
YELLOW,
BLACK,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
// Basic Broadcast - circle emanates outward with fading copies
const circle = new Circle({ radius: 1.5, color: TEAL_A });
scene.add(circle);
await scene.play(new Create(circle));
await scene.play(new Broadcast(circle));
await scene.wait(0.5);
// Broadcast with custom options on a square
await scene.play(new FadeOut(circle));
const square = new Square({ sideLength: 2, color: YELLOW });
scene.add(square);
await scene.play(new FadeIn(square));
await scene.play(
new Broadcast(square, {
nMobs: 3,
initialOpacity: 0.8,
lagRatio: 0.3,
duration: 2,
}),
);
await scene.wait(0.5);
Learn More: Broadcast · Circle · Create · FadeIn
LabeledPolygram
A LabeledPolygram places a label at the pole of inaccessibility of a polygon -- the interior point farthest from any edge. Supports polygons with holes via multiple vertex groups.
Source Code
import {
Scene,
Polygon,
LabeledPolygram,
Circle,
FadeIn,
Create,
BLACK,
BLUE,
WHITE,
} from 'manim-web';
const scene = new Scene(document.getElementById('container'), {
width: 800,
height: 450,
backgroundColor: BLACK,
});
// Define polygon with holes
const ring1 = [
[-3.8, -2.4, 0], [-2.4, -2.5, 0], [-1.3, -1.6, 0], [-0.2, -1.7, 0],
[1.7, -2.5, 0], [2.9, -2.6, 0], [3.5, -1.5, 0], [4.9, -1.4, 0],
[4.5, 0.2, 0], [4.7, 1.6, 0], [3.5, 2.4, 0], [1.1, 2.5, 0],
[-0.1, 0.9, 0], [-1.2, 0.5, 0], [-1.6, 0.7, 0], [-1.4, 1.9, 0],
[-2.6, 2.6, 0], [-4.4, 1.2, 0], [-4.9, -0.8, 0],
];
const ring2 = [
[0.2, -1.2, 0], [0.9, -1.2, 0], [1.4, -2.0, 0], [2.1, -1.6, 0],
[2.2, -0.5, 0], [1.4, 0.0, 0], [0.4, -0.2, 0],
];
const ring3 = [[-2.7, 1.4, 0], [-2.3, 1.7, 0], [-2.8, 1.9, 0]];
// Add polygon with holes to scene
const outerPoly = new Polygon({
vertices: ring1, color: BLUE, fillOpacity: 0.75, strokeWidth: 2,
});
const hole1 = new Polygon({
vertices: ring2, color: BLACK, fillOpacity: 1, strokeWidth: 0,
});
const hole2 = new Polygon({
vertices: ring3, color: BLACK, fillOpacity: 1, strokeWidth: 0,
});
scene.add(outerPoly, hole1, hole2);
// Label fades in at the pole of inaccessibility
const labeled = new LabeledPolygram({
vertexGroups: [ring1, ring2, ring3],
label: 'Pole',
precision: 0.01,
labelFontSize: 28,
labelColor: WHITE,
});
await scene.play(new FadeIn(labeled, { duration: 1 }));
// Draw reference circle at the pole
const circle = new Circle({
radius: labeled.radius,
color: WHITE,
center: [labeled.pole[0], labeled.pole[1], 0],
});
await scene.play(new Create(circle, { duration: 0.8 }));
Learn More: LabeledPolygram · Polygram · Polygon · FadeIn