Skip to main content
Technical preview

Redraw is currently in technical preview, available to wcandillon.dev subscribers. API is unstable.

Hello World

Backend Initialization

Redraw is built on top of WebGPU, so every program starts by acquiring a GPUDevice and wrapping it in a backend. The convenience entry point is RedrawInit, which requests an adapter and device for you:

import { RedrawInit } from "redraw";

const redraw = await RedrawInit();
const surface = redraw.makeSurfaceFromCanvas(canvasEl);

If you already have a GPUDevice (for example one shared with another WebGPU library, or the device exposed by react-native-webgpu's useDevice()), construct the backend directly and hand it to Redraw:

import { Redraw, WebGPUBackend } from "redraw";

// `device` is a GPUDevice you obtained elsewhere.
const backend = new WebGPUBackend(device);
const redraw = new Redraw(backend);

When you bring your own device, Redraw will not destroy it on teardown. That stays your responsibility.

Render & Animate

A Redraw scene has two phases. render builds an immutable list of drawing commands (geometry, brushes, paths) and returns handles to the nodes you want to mutate later. animate is then called every frame with those handles and an animation context (time, frame number, canvas size), updating them in place. animate is optional; a static scene needs only render.

The module below illustrates the pattern with two exported functions, render and animate.

// hello-world.ts
import type { Canvas, AnimationContext } from "redraw";
import {
fitPath,
GradientAlongPath,
SingleStrokeBrush,
animation,
} from "redraw";

const helloPath =
"M13.6 247.8C13.6 247.8 51.8 206.1 84.2 168.8 140.8 103.4 202.8 27.1 150.1 14.3 131 9.7 116.4 29.3 107.3 44.8 69.7 108.4 58 213.8 57.5 302M58 302C67.7 271.3 104.4 190.3 140.2 192.5 181.5 195.1 145.3 257 154.5 283.8 168.8 321.6 208.2 292.3 230 276.9 265.9 251.5 289 230.7 289 199.9 289 161 235.3 173.5 223.3 204.6 213.9 228.9 214.3 265.3 229.3 283.6 247.5 305.7 287.7 309.4 312.2 287.9 337 266.2 354.7 234 368.7 212.5 403.9 158.3 464.4 85.6 449.1 29.5 447 21.9 440.4 16 432.5 15.7 393.6 14.2 381.8 98.6 375.3 128.8 368.8 159.3 345.2 260.8 373.1 292.5 404.4 328 446.3 261.9 464.7 231.1 468.7 224.8 472.6 217.9 476.1 212.5 511.3 158.4 571.8 85.6 556.5 29.5 554.4 21.9 547.8 16.1 539.9 15.8 501 14.2 489.2 98.7 482.8 128.8 476.2 159.3 452.6 260.8 480.5 292.6 511.8 328.1 562.4 265 572.6 232.3 587.3 185.4 620.9 171 660.9 179.7M660.9 179.7C616 166.1 580.9 199.1 572.6 232.6 566.8 256.4 573.5 281.6 599.2 295.2 668.5 331.9 742.8 211.1 660.9 179.7ZM660.9 179.7C643.7 181.3 636.1 204.2 643.3 227.2 654.3 263.4 704.3 267.7 733.1 255.5";

const palette = [
"#3FCEBC",
"#3CBCEB",
"#5F96E7",
"#816FE3",
"#9F5EE2",
"#DE589F",
"#FF645E",
"#FDA859",
"#FAEC54",
"#9EE671",
"#41E08D",
];

// Immutable drawing instructions with support for animations
export function render(canvas: Canvas, width: number, height: number) {
canvas.fillColor("white");
// 1. Scale the path
const pathGeo = fitPath(helloPath, width, height, { maxWidth: 800 });
// 2. Make a brush: color along path, fixed stroke width of 25
const brush = new SingleStrokeBrush();
brush.addStroke(new GradientAlongPath(palette), 25);
// 3. drawPath
const path = canvas.drawPath(pathGeo, brush);
// 4. return the objects that will be used at animation time
return { path };
}

// Animate the path stroke being drawn over time
export function animate(
{ path }: ReturnType<typeof render>,
ctx: AnimationContext,
) {
const t = animation(ctx, { duration: 8, boomerang: true });
path.segment(0, t);
}
Hello, RedrawOpen in editor →

The rest of this page wires that same render/animate pair into three hosts. The drawing code never changes; only the canvas plumbing does.

Vanilla JS

In a static page, you own the canvas element, the WebGPU init, and the animation loop:

<!-- index.html -->
<!DOCTYPE html>
<html>
<body style="margin: 0">
<canvas id="my-canvas-id" style="display: block; width: 100vw; height: 100vh"></canvas>
<script type="module" src="./main.ts"></script>
</body>
</html>
// main.ts
import { RedrawInit, Canvas } from "redraw";
import { render, animate } from "./hello-world";

// 1. Initialize the WebGPU backend.
const redraw = await RedrawInit();

// 2. Bind a Surface to the canvas element, looked up by id. The backing
// store is sized to its CSS size times the device pixel ratio
// automatically; pass { autoResize: false } to manage
// canvas.width/height yourself.
const surface = redraw.makeSurfaceFromCanvas("my-canvas-id");

// 3. Build the scene once. Keep handles to nodes we want to animate.
// Scene coordinates are CSS pixels; the Canvas dpr scale maps them to
// physical pixels.
const { width, height } = surface;
const canvas = new Canvas({ dpr: window.devicePixelRatio || 1 });
const nodes = render(canvas, width, height);

// 4. Drive the animation loop. Mutate scene parameters and re-flush each frame.
let frame = 0;
const start = performance.now() / 1000;
const tick = () => {
const time = performance.now() / 1000 - start;
animate(nodes, { time, frame: frame++, width, height });
surface.flush(canvas);
requestAnimationFrame(tick);
};
tick();

Using React

This page drives the loop by hand to show how the pieces fit. In an application you will usually let the bindings own the WebGPU lifecycle and the animation loop instead: see the React and React Native guides.