Skip to main content
Technical preview

Redraw is currently in technical preview, available to start-react-native.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-wgpu'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="canvas" 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";

const canvasEl = document.getElementById("canvas") as HTMLCanvasElement;

const dpr = window.devicePixelRatio || 1;
const rect = canvasEl.getBoundingClientRect();
const width = rect.width;
const height = rect.height;
canvasEl.width = Math.round(width * dpr);
canvasEl.height = Math.round(height * dpr);

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

// 2. Bind a Surface to the HTML canvas element.
const surface = redraw.makeSurfaceFromCanvas(canvasEl);

// 3. Build the scene once. Keep handles to nodes we want to animate.
const canvas = new Canvas({ dpr });
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();

React

react-redraw exposes a <RedrawCanvas> component that owns the WebGPU lifecycle and the animation loop. Pass the same render and animate functions as props:

// Hello.tsx
import { RedrawCanvas } from "react-redraw";

import { render, animate } from "./hello-world";

export function Hello() {
return (
<RedrawCanvas
style={{ width: "100%", aspectRatio: "4 / 3" }}
render={render}
animate={animate}
/>
);
}

React Native

react-native-redraw ships an identical <RedrawCanvas> API. Only the import path and the style system change:

// Hello.tsx
import { StyleSheet } from "react-native";
import { RedrawCanvas } from "react-native-redraw";

import { render, animate } from "./hello-world";

export function Hello() {
return (
<RedrawCanvas style={styles.canvas} render={render} animate={animate} />
);
}

const styles = StyleSheet.create({
canvas: {
flex: 1,
},
});