Two.js: One 2D Drawing API for SVG, Canvas2D and WebGL
A renderer agnostic two-dimensional drawing api for the web
At a glance
- What is it?
- Two.js is a renderer agnostic two-dimensional drawing API for the browser. The same scenegraph code can target SVG, Canvas2D or WebGL, and the project has been shipping since 2012 under the MIT licence.
- Who is it for?
- Two.js fits projects that need one 2D scenegraph to run in more than one rendering context, especially demos, data visualisation and motion graphics that may start on SVG and later move to Canvas2D or WebGL.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- jonobr1/two.js8,716 ★ · +55
| Repository | 2026-09-22 | 2026-11-14 |
|---|---|---|
| jonobr1/two.js | 8.7k | 8.7k |
55 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
What Two.js solves, and for whom
Most browser drawing libraries commit you to one rendering context. If you start with SVG for crisp vector output and later need the throughput of WebGL, you usually rewrite the drawing layer. Two.js takes the opposite position: the README describes it as "a two-dimensional drawing api meant for modern browsers" that is "renderer agnostic enabling the same api to render in multiple contexts: webgl, canvas2d, and svg". The scenegraph you build with makeRectangle, makeCircle and similar calls is independent of the renderer that draws it.
That matters for a specific kind of project. A dashboard can use the same drawing API with different renderers, but supported features and output can differ; test the scene and interactions on the chosen backend before switching. A motion graphics piece can be authored once and exported to whichever context the target device handles best. The package keywords list the intended territory: svg, canvas2d, webgl, animation, scenegraph, motiongraphics, visualization, bitmap and vector.
The project is not new. The README states that Two.js "has been in operation since 2012", and the changelog in the repository spans from a first alpha built with Three.js to the current release. That history is visible in the release cadence: v0.8.20 in September 2025, v0.8.21 in October 2025, and v0.8.24 in August 2026. The last push to the default dev branch was on 2026-10-31, so the repository is active rather than dormant.
How the scenegraph and the three renderers fit together
A Two.js program starts by constructing a Two instance and appending it to a DOM element. That instance owns a scenegraph: shapes created through makeRectangle, makeCircle and the rest are nodes in a tree, and the instance also owns a renderer object exposed as two.renderer, with a domElement property that the React example in the README removes on unmount.
The README's boilerplate does not show how the renderer is chosen, though the renderer-agnostic claim depends on that choice. Because the scenegraph is separate from the drawing backend, the same node tree can be handed to the SVG renderer for DOM-inspectable output, to Canvas2D for a single bitmap surface, or to WebGL for GPU drawing. Animation follows the same split: two.bind('update', callback) registers a per-frame callback, and the callback mutates node properties such as rotation. The renderer reads those properties on the next frame. The README's spinning rectangle does exactly this, incrementing rect.rotation by 0.001 inside the update handler.
Two smaller mechanisms are worth knowing. The build is modular: the README explains that if you only use the SVGRenderer you can "really cut down on the file size by excluding the other renderers" by editing /utils/build.js and running node ./utils/build. And in Node, Two.Utils.polyfill(canvas, Image) wires a Node Canvas instance into Two.js so the library can render without a browser.
Installing Two.js and drawing a first shape
The README gives two installation routes. The quickest is to download the minified build and include it with a script tag. The npm route is the one most projects will take.
npm install --save two.jsAfter that, the boilerplate in the README creates a fullscreen instance and a rectangle, then rotates it on every frame. The autostart option starts the animation loop without a separate play call.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="js/two.min.js"></script>
</head>
<body>
<script>
var two = new Two({
fullscreen: true,
autostart: true
}).appendTo(document.body);
var rect = two.makeRectangle(two.width / 2, two.height / 2, 50 ,50);
two.bind('update', function() {
rect.rotation += 0.001;
});
</script>
</body>
</html>In a bundler, the ES module import works from version v0.7.5 onward. The README's React example binds the update handler, and its unmount function calls two.unbind("update"), two.pause() and removeChild on two.renderer.domElement. That cleanup sequence is the part people forget; without it the animation loop keeps running after the component is gone.
import React, { useEffect, useRef } from "react";
import Two from "two.js";
export default function App() {
var domElement = useRef();
useEffect(setup, []);
function setup() {
var two = new Two({
fullscreen: true,
autostart: true
}).appendTo(domElement.current);
var rect = two.makeRectangle(two.width / 2, two.height / 2, 50, 50);
two.bind("update", update);
return unmount;
function unmount() {
two.unbind("update");
two.pause();
domElement.current.removeChild(two.renderer.domElement);
}
function update() {
rect.rotation += 0.001;
}
}
return <div ref={domElement} />;
}The package.json points main at build/two.js and module at build/two.module.js, so a bundler that understands the module field gets the ES module build. To run the same code on a server, the README's headless path installs Node Canvas alongside Two.js.
npm install canvas
npm install two.jsTree shaking, bundle size and the import you actually get
The README is unusually direct about a limitation that most libraries bury. It states that the main import of the Two namespace imports all modules, that "there isn't yet proper tree shaking implemented for the library", and that tree shaking is "on the roadmap". Importing Two.js therefore pulls in the whole namespace even if your page only ever draws with the SVG renderer.
The workarounds are manual. One is the custom build described in the README: edit /utils/build.js to include only the renderer you use, then run node ./utils/build, which rewrites /build/two.js and /build/two.min.js. The other is importing individual modules from source, which the README shows with Vector.
import { Vector } from 'two.js/src/vector.js';
// In TypeScript environments leave out the ".js"
// when importing modules directly. e.g:
import { Vector } from 'two.js/src/vector';That second route means you bundle and minify the modules yourself. It is a real cost for a small visualisation, and it is the main reason to think twice before reaching for Two.js on a page where a few inline SVG elements would do. The README does not give bundle size figures, only a bundlephobia badge in the header.
Where Two.js is the wrong tool
Two.js is a two-dimensional API. If the scene needs depth, perspective or a 3D camera, the scenegraph model here does not provide it, and the README's own history notes that the project began as something built with Three.js before becoming its own 2D library. Reaching for Two.js and faking depth is more work than starting with a 3D renderer.
Headless rendering is possible but conditional. The README says Two.js can run "in a headless environment, namely running on the server with the help of a library called Node Canvas", and that Node Canvas is deliberately not a dependency because it is "not necessary" in the browser. Server-side rendering inherits Node Canvas's native dependency requirements. A compatible prebuilt binary may be available for some platforms; otherwise a native build is needed. Check the Node Canvas installation guidance for the target runtime.
Version pinning deserves attention too. The package.json version string is "v0.8.24", with a leading v, while npm version fields conventionally omit it. More importantly, the README and changelog describe releases but the README does not document a rollback procedure or a support window for older releases. If you adopt a specific version, the changelog under wiki/changelog is the only upgrade record you have.
Two.js compared with PixiJS and Three.js
The two comparisons that come up most are with PixiJS and Three.js, and they differ in approach rather than in quality.
PixiJS is built around WebGL as the primary renderer. Two.js inverts that: SVG, Canvas2D and WebGL are all first-class targets selected through the same API, so an SVG page and a WebGL page can share one scenegraph and one animation callback. The practical difference shows up in output. SVG from Two.js is DOM nodes you can inspect, style with CSS and select with developer tools; a WebGL canvas from either library is a bitmap you cannot query element by element. If you need accessible, selectable vector output as a first-class target rather than a degraded fallback, that is the argument for Two.js. If you need maximum draw throughput and never intend to ship SVG, PixiJS's WebGL-first design is the more direct route.
Three.js is a 3D engine, and Two.js does not compete with it. The overlap is only historical: the README notes that Two.js's first alpha was built with Three.js. A project that needs a 3D scene, lighting and a perspective camera should use a 3D engine; a project that needs rectangles, circles, paths and text arranged on a plane should not carry a 3D engine to draw them.
Licence, maintenance and upgrade cost
Two.js is MIT licensed, per the LICENSE file and the license field in package.json. MIT is permissive: it allows commercial and closed-source use, and it requires that the copyright notice and permission notice be included in copies or substantial portions of the software. That is a summary of the licence text, not legal advice; check the LICENSE file in the repository for the exact wording before relying on it.
The repository is not archived, and the last push to the dev branch was on 2026-10-31. Three releases are listed: v0.8.20 on 2025-09-08, v0.8.21 on 2025-10-02, and v0.8.24 on 2026-08-29. The gap between v0.8.21 and v0.8.24 is roughly eleven months, so release cadence is uneven rather than regular, even though the branch itself sees commits. Anyone planning around a fixed upgrade schedule should read that as "releases arrive when they arrive".
Upgrade cost has a few concrete components. The changelog lives under wiki/changelog in the repository, and that is where breaking changes would be recorded. The build is regenerated by node ./utils/build, so a custom build has to be re-run after an upgrade or it will silently stay on the old renderer set. The documentation site is generated from README.md files in the wiki directory with npm run docs:generate, and the README specifies Node 24 LTS for that toolchain with an .nvmrc file to match, so contributing to docs pins you to that Node version. Type definitions ship in the package at src/two.d.ts, and there is a types:check script that runs tsc against tsconfig.types.json, which means type regressions are at least checkable by the maintainer before release.
Editorial conclusion
Two.js fits projects that need one 2D scenegraph to run in more than one rendering context, especially demos, data visualisation and motion graphics that may start on SVG and later move to Canvas2D or WebGL. It is a poor fit if you need tree shaking today (the README states the main import pulls in all modules and that tree shaking is only on the roadmap), if you need a documented rollback path between releases, or if you are building a 3D scene rather than a two-dimensional one. Before adopting it, verify the exact version installed with npm ls two.js, confirm that your bundler resolves the module field in package.json to build/two.module.js, and check the changelog in the wiki directory for the release you are moving to.
Frequently asked questions
What is Two.js?
Two.js is a two-dimensional drawing API for modern browsers that is renderer agnostic, so the same API can render through WebGL, Canvas2D or SVG. The README describes it as having been in operation since 2012, and it is published on npm as two.js.
How does Two.js compare with PixiJS?
The README does not mention PixiJS. What the repository establishes is that Two.js treats SVG, Canvas2D and WebGL as interchangeable targets for one scenegraph, whereas a WebGL-first library makes the GPU path primary and treats other contexts as fallbacks. The choice comes down to whether SVG output needs to be a first-class target for your project.
How does Two.js compare with Three.js?
Two.js is a 2D API and does not offer a 3D scene, camera or lighting. The README notes that Two.js's first alpha version was built with Three.js before the project became its own two-dimensional library, so the two now address different problems rather than competing.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jonobr1-two-js)Star history chart for your README
[](https://hysenlabs.com/projects/jonobr1-two-js)Was this useful?