ivarvong
lantern
Animated, steppable code explainers. Zero-dependency TypeScript, SVG output, browser and Cloudflare Workers.
Documentation snapshot
README 快照
翻译暂时拿不到。
机器翻译的项目简介,仅供参考。原文在下方,也可以直接用浏览器自带的整页翻译 (Chrome / Edge 点地址栏右侧的翻译图标,或用右键菜单里的「翻译成中文」)。
下面正文是项目自己的英文 README。想读全文就用浏览器自带的整页翻译: Chrome / Edge 点地址栏右侧的翻译图标,或用右键菜单里的「翻译成中文」; 手机浏览器一般在菜单里。
本页保存的是公开项目资料快照,阅读过程不需要连接 GitHub。
lantern
Animated, steppable explainers for code changes. A zero-dependency JS port of
manim’s core model that renders to SVG: a live DOM in the browser, strings in
Cloudflare Workers or Node. Every step is addressable (?scene=&step=), and
every scene has a text form and a structured trace for agents.
Quickstart
git clone https://github.com/ivarvong/lantern && cd lantern # not on npm yet; zero runtime dependencies
Then:
The repo is TypeScript throughout. Node 24 runs the tools and tests as .ts directly
(type stripping, so only erasable syntax); tsc compiles the library to dist/.
npm install
npm run dev # http://localhost:5173/?scene=pyex-limits, reloads on save, keeps your step
npm test # ~8 s; FULL=1 (npm run test:full) checks every frame
npm run check # tsc over every project, emits dist/
A scene is a class with an async construct(). Steps are markers; this.state is
what the explanation is about, and it is what traces record:
import { Scene, Rectangle, Text, Labeled, Arrow, FadeIn, Create, RIGHT, TEAL, GOLD } from '../src/index.ts';
export class CacheScene extends Scene {
override async construct() {
const st = (this.state = { cache: false });
await this.step('before');
const handler = new Labeled(new Rectangle({ width: 3, height: 1, stroke: TEAL }), new Text('handler'));
const db = new Labeled(new Rectangle({ width: 2, height: 1, stroke: GOLD }), new Text('D1'));
db.nextTo(handler, RIGHT, 2);
await this.play(new FadeIn(handler), new FadeIn(db));
await this.play(new Create(new Arrow(handler.shape.right(), db.shape.left(), { buff: 0.1 })));
await this.step('after', { pr: 482 });
st.cache = true;
// ...
}
}
Register it in scenes/index.ts, then:
npm run see -- my-scene after # the frame as text, plus the step's state diff
npm run shot -- my-scene # contact sheet: the end of every step, as a PNG
npm run verify:motion -- my-scene # teleports, pops, cuts, flicker
Diagrams can be text: flowchart(mermaidSource) parses a Mermaid flowchart, lays it out
and returns shapes you can animate (docs/FLOWCHART.md). Scenes can also be
data: sceneFromDoc(doc) plays a JSON document of nodes, edges,
code and ops (docs/DOC-FORMAT.md).
Model
- A Mobject is a tree of
Float32Arraypoint buffers (stride 2) plus a style record. Every geometric operation maps a function over the family’s points: points are the truth. - A VMobject is a list of cubic Bézier subpaths
[anchor, (h1, h2, anchor)*], so curve n starts at point 3n. The serializer and the alignment code share that one contract. - Text is glyph outlines (Inter, JetBrains Mono, embedded), one VMobject per glyph: exact
advances, letter-by-letter
Write, andTransformbetween strings morphs glyph to glyph. Nothing depends on installed fonts, so every rasterizer draws the same thing. - An Animation is a pure function of alpha over two endpoints fixed in
begin().Transformfirst aligns structure (null copies, de Casteljau subdivision) so both ends have equal shape; each frame is then a lerp, field by field, style included. - A Scene advances a fixed
dt = 1/fpsand hands itself to a sink, which decides what a frame is:StringSinkcollects SVG,DomSinkpatches a live “ on rAF time,MotionSinksamples boxes for the verifier,TraceSinkrecords state at step boundaries. Output is deterministic. - Seeking re-runs the scene to
twith frames skipped (manim’sskip_animations): the loop is fixed-dt and the endpoints are fixed atbegin(), so a seek is byte-identical to a full run. Tests assert this for every scene.
Coordinates are manim’s: the frame is 16 × 9 units, origin at the center, y up
(UP = [0, 1], angles counterclockwise). The flip to SVG’s y-down happens in one place,
where path data is written (VMobject.roundedRelative).
Layout
src/ the library: no dependencies, no runtime assumptions (browser, Workers, Node)
index.ts the public API, curated, types included; anything not exported here is internal
dist/ src/ compiled by tsc: ESM .js, .d.ts, maps. What the package exports (npm run build:lib)
scenes/ example scenes and the registry (index.ts)
app/ the player (player.ts, DomSink) and the Worker (worker.ts): the only runtime-specific code
tools/ dev server, build, inspection, verifiers, perf tools (Node; Chromium via playwright-core)
test/ node --test; golden-traces.json hashes each scene's trace (UPDATE_GOLDEN=1 to bless)
vendor/ the glyph shaper bundle (tools/vendor-glyph.ts) and its types
docs/ PERF.md, VERIFY.md, DOC-FORMAT.md
Outputs
| how | |
|---|---|
| live, in a page | app/player.ts: ?scene=&step= deep links, step mode, copy link |
| one frame as SVG | renderAt(Scene, t, new SnapshotSink()), or Worker GET /frame.svg?scene=&step= |
| one frame as text | renderText(scene), npm run see, or Worker GET /text?scene=&step= |
| state at every step | traceScene(Scene), or Worker GET /trace.json?scene= |
| every frame | npm run render -- writes out/frames//*.svg |
Deploy: npm run deploy (builds out/site/, then wrangler deploy).
Commands
npm run dev | source tree as native ES modules, live reload |
npm run build | single-file player pages in out/ (esbuild, ~80 ms) |
npm test / test:full | sampled frames / every frame |
npm run check | tsc -b over every project (library, app, tools and tests), emit dist/, compile a TypeScript consumer against it |
npm run build:lib | dist/ from scratch; prepack runs it |
npm run verify | motion verifier, then the playback gate in Chromium (docs/VERIFY.md) |
npm run bench | every scene timed against bench/baseline.json; fails on a reproduced regression |
MANIM_DIR=… npm test | also diffs rate functions and curve subdivision against manim’s own code |
npm run see, shot, render | look at a scene: text, contact sheet, SVG frames |
npm run trace, profile, seek, ab, hitch | performance (docs/PERF.md) |
npm run fonts | re-subset the embedded fonts (needs pip install fonttools brotli) |
Out of scope, for now
- Matching morphs (
TransformMatchingShapes): glyphs pair by index, not by shape. - 3D, boolean path ops, LaTeX.
- Rigid moves as SVG
transformattributes: every move rewrites points. - Collaboration: the trace and doc formats are diffable, but nothing syncs them yet.
Official distribution
获取与安装
暂未发现可确认的官方软件包地址
当前 README 快照没有出现 npm、PyPI、Crates.io、pub.dev 等官方包页链接。本站不会根据仓库名称猜测下载地址。
本站不托管项目文件;需要安装时,请以项目维护者发布的官方文档为准。
Before installing
使用前核验
本站保存公开资料用于阅读,不代表安全审计或功能背书。安装前请核对许可证、依赖来源和发布签名,不要直接运行来源不明的二进制文件或高权限脚本。