Browse by type
Ripl (pronounced "ripple") is a zero-dependency TypeScript charting, drawing and animation library built on a unified API for 2D graphics rendering. Write a scene once and render it to Canvas, SVG, a terminal (as braille and ANSI colour), or a Node process, with 3D on Canvas and WebGPU alongside.
The Canvas API is low-level: it has no concept of objects, hierarchy, or events. SVG is easier to reason about but carries its own performance limits and API differences. The two diverge enough that projects usually commit to one at the outset. Ripl removes that decision by exposing one API over both, modelled on the DOM and CSSOM, so switching contexts is a one-line change.
Spline Polyline
![]() |
3D Sphere
![]() |
Multi-Line Graph
![]() |
Bubble Scatter
![]() |
Candlestick Chart
![]() |
3D Jet Engine
![]() |
requestAnimationFrame loopgetElementById, getElementsByType, getElementsByClass, plus query/queryAll with selector syntaxgetBoundingBox on all shape elementsImageData), an object URL, or a string (PNG data URL / SVG markup / terminal text)Shape2Dpattern(...) paint strings (diagonal, cross-hatch, dots, horizontal, vertical) in fills and strokesTask-based transitions with CSS-like keyframe support and custom interpolatorsscaleLog/scaleSqrt shortcuts@ripl/charts: line, bar, area, trend, scatter, histogram, box plot, stock (candlestick/OHLC), realtime, pie/donut, polar area, polar scatter, radial bar, radar, gauge, heatmap, treemap, sunburst, packed circle, sankey, chord, arc diagram, force-directed, funnel and ganttcreate*Light factory; directional and spot lights are either fixed in world space or carried by the cameracolor, opacity, emissive/emissiveIntensity, Blinn-Phong specular/shininess, side (front/back/double), wireframe, flatShading, vertexColors, and a texture mapclamp/repeat/mirror wrapping, nearest/linear filtering, and a repeat/offset UV transform, sampled the same way by the Canvas and WebGPU backends@ripl/devtools) with a companion browser extension for live scene-graph inspection and editing| Package | Description |
|---|---|
@ripl/web |
Main entry point for browser usage. Re-exports core + canvas context with browser platform bindings |
@ripl/core |
Core rendering: elements, scene, renderer, animation, scales, math, color, interpolation, gradients, tasks |
@ripl/canvas |
Canvas 2D rendering context |
@ripl/svg |
SVG rendering context |
@ripl/charts |
Pre-built chart components with axes, legends, tooltips, crosshairs, and grids |
@ripl/3d |
3D rendering context with camera, shading, and primitive shapes |
@ripl/webgpu |
WebGPU-accelerated 3D rendering context with hardware depth testing and WGSL shaders |
@ripl/terminal |
Terminal rendering context with braille-character output and ANSI truecolor |
@ripl/node |
Node.js runtime bindings that configure the platform factory for headless environments |
@ripl/devtools |
Page-side devtools bridge that streams the scene graph to the browser extension for live inspection |
@ripl/dom |
DOM utilities used internally by browser contexts |
@ripl/utilities |
Shared typed utility functions: type guards, collection helpers, DOM helpers |
The project is structured as a Yarn 4 monorepo:
packages/
├── core/ # Core rendering library
├── canvas/ # Canvas 2D rendering context
├── svg/ # SVG context implementation
├── charts/ # Pre-built chart components
├── 3d/ # 3D rendering
├── webgpu/ # WebGPU 3D rendering context
├── terminal/ # Terminal rendering context
├── node/ # Node.js runtime bindings
├── web/ # Main browser entry point
├── devtools/ # Page-side devtools bridge
├── dom/ # DOM utilities
├── utilities/ # Shared typed utility functions
└── test-utils/ # Test utilities
apps/website/ # Documentation site (VitePress) with live demos
The following is a tour of Ripl's features starting from the most basic and progressively building towards more advanced concepts.
import {
createCircle,
createContext,
} from '@ripl/web';
// Create a canvas context bound to a DOM element
const context = createContext('.mount-element');
// Create an element
const circle = createCircle({
fill: 'rgb(30, 105, 120)',
lineWidth: 4,
cx: context.width / 2,
cy: context.height / 2,
radius: context.width / 3,
});
// Render the element to the context
circle.render(context);
Built-in 2D shape primitives: arc, circle, rect, line, polyline, polygon, ellipse, text, path, image.
To modify an element, change any of its properties and re-render.
circle.fill = '#FF0000';
circle.cx = context.width / 3;
circle.cy = context.height / 3;
circle.render(context);
To render the same element to SVG, replace the createContext import from @ripl/web with @ripl/svg:
import {
createContext,
} from '@ripl/svg';
import {
createCircle,
} from '@ripl/web';
const context = createContext('.mount-element');
const circle = createCircle({ /* same options */ });
circle.render(context);
Render multiple elements in groups with inherited properties (like CSS) and event bubbling (like the DOM):
import {
createCircle,
createContext,
createGroup,
createRect,
} from '@ripl/web';
const context = createContext('.mount-element');
const circle = createCircle({
cx: context.width / 2,
cy: context.height / 2,
radius: context.width / 3,
});
const rect = createRect({
x: context.width / 2,
y: context.height / 2,
width: context.width / 5,
height: context.height / 5,
});
// Both children inherit fill and lineWidth from the group
const group = createGroup({
fill: 'rgb(30, 105, 120)',
lineWidth: 4,
children: [circle, rect],
});
group.render(context);
Elements can be queried using common DOM methods or CSS-like selectors:
const circles = parentGroup.getElementsByType('circle');
const shapes = parentGroup.queryAll('.shape');
const first = parentGroup.query('#child-group > .shape');
Supported selector features:
circle /* type */
#element-id /* id */
.element-class /* class */
circle[radius="5"] /* attribute */
.group-class circle /* descendant */
.group-class > circle /* direct child */
.group-class rect + circle.circle-class /* adjacent sibling */
A Scene is the top-level group bound to a rendering context. A Renderer drives the animation loop via requestAnimationFrame.
import {
createCircle,
createGroup,
createRect,
createRenderer,
createScene,
} from '@ripl/web';
const circle = createCircle({
fill: 'rgb(30, 105, 120)',
cx: 100,
cy: 100,
radius: 40,
});
const rect = createRect({
fill: 'rgb(30, 105, 120)',
x: 200,
y: 80,
width: 60,
height: 60,
});
// Scene takes a target (selector, element, or context) and options
const scene = createScene('.mount-element', {
children: [circle, rect],
});
const renderer = createRenderer(scene, {
autoStart: true,
autoStop: true,
});
// Listen for events
circle.on('click', event => console.log(event));
The renderer provides transition-based animation. Transitions are cancelable Task instances (extending Promise with AbortController integration).
import {
easeOutCubic,
} from '@ripl/web';
// Animate a single element
await renderer.transition(circle, {
duration: 1000,
ease: easeOutCubic,
state: {
fill: '#FF0000',
cx: 200,
cy: 200,
radius: 60,
},
});
// Animate multiple elements (or a whole group/scene)
await renderer.transition([circle, rect], {
duration: 1000,
ease: easeOutCubic,
state: {
fill: '#FF0000',
},
});
// Implicit keyframe offsets
await renderer.transition(circle, {
duration: 1000,
ease: easeOutCubic,
state: {
fill: [
'#FF0000', // offset ~0.33
'#00FF00', // offset ~0.66
'#0000FF', // offset 1
],
},
});
// Explicit keyframe offsets
await renderer.transition(circle, {
duration: 1000,
ease: easeOutCubic,
state: {
fill: [
{
value: '#FF0000',
offset: 0.25,
},
{
value: '#0000FF',
offset: 0.8,
},
],
},
});
// Custom interpolator function
await renderer.transition(circle, {
duration: 1000,
ease: easeOutCubic,
state: {
radius: t => t * 100, // 0 <= t <= 1
},
});
Every element supports CSS-like transforms:
const rect = createRect({
x: 100,
y: 100,
width: 80,
height: 80,
rotation: '45deg', // or radians as a number
transformOriginX: '50%', // or pixels as a number
transformOriginY: '50%',
translateX: 20,
translateY: 10,
transformScaleX: 1.5,
transformScaleY: 1.5,
});
Transforms can also be animated via renderer.transition.
Every context can capture a snapshot of what it has rendered and export it to an image, URL, or string via export():
const snapshot = context.export();
const str = snapshot.toString(); // PNG data URL (SVG → markup, Terminal → braille text)
const url = snapshot.toURL(); // openable Blob object URL
const image = await snapshot.toImage(); // low-level ImageData (environment-agnostic)
The exact outputs depend on the context: Canvas, 3D, and WebGPU export raster images; SVG exports vector markup (and can rasterize to an image); Terminal exports braille text (and a rasterized image). Charts forward export() to their underlying context:
``
browse all types & interfaces →