Skip to main content
Technical preview

Redraw is currently in technical preview, available to start-react-native.dev subscribers. API is unstable.

Paths

Paths in Redraw split into two flavors: open paths and closed paths. Open paths have no interior to be filled; they exist only to be stroked. This is where the SingleStrokeBrush API comes in handy. Closed paths have a defined interior that can be filled.

Path kindDrawn withBest for
Open pathSingleStrokeBrush + drawPathCalligraphy, signatures, single-stroke animations.
Closed pathaddGeometry + drawGeometryFilled shapes, repeated draws of the same outline.

Building paths

You can build a path using SVG:

import { parseSVG } from "redraw";

const path = parseSVG("M 10 10 L 90 90 C 100 50, 50 50, 10 10 Z");

or using the path builder API:

import { PathBuilder, vec } from "redraw";

const cx = 100, cy = 100, size = 80;

const heart = new PathBuilder();
heart.moveTo(vec(cx, cy - size * 0.4));
heart.cubicTo(
vec(cx + size * 0.5, cy - size * 0.8),
vec(cx + size, cy - size * 0.2),
vec(cx, cy + size * 0.6),
);
heart.cubicTo(
vec(cx - size, cy - size * 0.2),
vec(cx - size * 0.5, cy - size * 0.8),
vec(cx, cy - size * 0.4),
);
heart.close();

// Paths are immutable
const path = heart.makePath();

PathBuilder is the imperative API: moveTo, lineTo, quadTo, cubicTo, close. Use close() when you want a closed contour; omit it for open ones. close() also starts a fresh contour, so chaining close().moveTo(...) works as expected for multi-contour paths.

Open paths

An open path doesn't enclose a region; there's no "interior", just a curve to stroke. The fastest way to render one is to pair parseSVG with fitPath, attach it to a SingleStrokeBrush, and call canvas.drawPath:

import { fitPath, SingleStrokeBrush, GradientAlongPath } from "redraw";

const pathGeo = fitPath(svgString, width, height, { maxWidth: 800 });
const brush = new SingleStrokeBrush();
brush.addStroke(new GradientAlongPath(palette), 25);
const path = canvas.drawPath(pathGeo, brush);

// Animate the reveal:
path.segment(0, progress);

drawPath returns a handle whose segment(t0, t1) method draws only the portion of the path between two arc-length parameters. Perfect for draw-in effects.

SingleStrokeBrush (see Brushes) is what unlocks the optimized path: the renderer skips interior evaluation and exposes the full geometry context (ctx.t, ctx.tan, ctx.grad) to your TypeGPU functions.

Animating a stroke reveal with segment()Open in editor →

Closed paths

A closed path encloses a region, and the renderer needs to evaluate the SDF over the whole interior. For shapes you draw repeatedly (say, an animated heart with multiple ghost outlines, or a static logo behind a gradient), build the geometry once and reuse it:

const heartPath = createHeartPath(cx, cy, baseSize); // closed Path
const geometry = canvas.addGeometry(heartPath);

// Now draw it many times with different brushes:
canvas.drawGeometry(geometry, mainBrush);
canvas.drawGeometry(geometry, glowBrush);
for (const ghost of ghosts) {
canvas.pushTransform();
canvas.transform(ghost.transform);
canvas.drawGeometry(geometry, ghost.brush);
canvas.popTransform();
}

addGeometry rasterizes the path's SDF into a tile once and uploads it to the GPU. drawGeometry then samples from that buffer, much cheaper than rebuilding the SDF for each draw. The win compounds when you use the same shape with multiple brushes or under a transform stack.

The Heartbeat demo below builds a closed heart path, calls addGeometry once, then drawGeometry for the main fill, the inner glow, and five ghost outlines that spawn on each beat, all sharing one geometry buffer:

One geometry, many draws: main fill, glow, and 5 animated ghostsOpen in editor →

Path helpers

These are available on any Path instance, regardless of whether you draw it as open or closed:

  • path.bounds(): axis-aligned bounding box.
  • path.segment(t0, t1): sub-path between two arc-length parameters [0, 1].
  • path.splitContours(): array of single-contour paths. Useful when an SVG has multiple disconnected sub-paths and you want them rendered independently.
  • path.fit(mode, src, dst): explicit fit ("contain" / "cover" / "fill").

And as module-level functions:

  • fitPath(svgString, w, h, { padding, maxWidth }): convenient parse-and-fit-to-canvas wrapper.
  • fitbox(mode, src, dst): returns the fit matrix without applying it.