Skip to content
pixi-silk
GitHub repository
Editor kit

Path editing

Edit bezier paths in PixiJS: drag anchors and handles, add and delete points, draw new paths with a pen tool, and undo every edit as one step.

Updated pixi-silk 1.0.1View as Markdown

Drag points and handles, click the outline to add a point. Pen draws a new path.

pixi-silk/path holds bezier paths as plain data and the helpers that edit them. pixi-silk/ui/pathgizmo edits a path on screen with anchors, handles and a pen.

Describe a path#

A path is a list of anchors and a closed flag. Each anchor has a position, optional in and out handles (offsets from the anchor) and a type.

import type { BezierPath } from 'pixi-silk/path';

const drop: BezierPath = {
    closed: true,
    anchors: [
        { x: 0, y: -80, type: 'corner' },
        { x: 50, y: 20, in: { x: 0, y: -40 }, out: { x: 0, y: 40 }, type: 'smooth' },
        { x: -50, y: 20, in: { x: 0, y: 40 }, out: { x: 0, y: -40 }, type: 'smooth' },
    ],
};
TypeHandles
cornerEach handle moves on its own. With no handles the anchor is a sharp corner.
smoothThe two handles stay on one line, each with its own length.
mirroredThe two handles stay on one line and have the same length.

The path fits in JSON, in a signal and in an undo step as it is.

Draw it#

import { linear, SilkGraphics } from 'pixi-silk';
import { type BezierPath, drawPath } from 'pixi-silk/path';

declare const drop: BezierPath;

const g = new SilkGraphics();

drawPath(g, drop).fill(linear([0x64d2ff, 0x0a84ff])).stroke({ width: 2, color: 0xffffff });

drawPath(g, path) calls moveTo, bezierCurveTo and closePath on the graphics and returns it, so fill() and stroke() chain on. A free path fills even-odd. See Lines and paths.

Edit it from code#

The helpers never change their input. Each one returns a new path.

HelperWhat it does
insertAnchor(path, segment, t)Splits a segment at t with de Casteljau’s construction. The curve keeps its shape.
removeAnchors, moveAnchorsDelete or move a set of anchors.
setHandleMoves one handle and keeps the twin in line on smooth and mirrored anchors.
setAnchorType, toggleAnchorTypeSwitch between corners and curves. A corner with no handles grows them from its neighbours.
nearestOnPath, pathBounds, anchorsInRectMeasure and hit-test.
parseSvgPath, pathToSvgRead SVG path data, arcs and quadratics included, and write it back.
mapPathMoves a path through an affine map.

Edit it on screen#

import { Container } from 'pixi.js';
import { SilkGraphics } from 'pixi-silk';
import { History } from 'pixi-silk/history';
import { type BezierPath, drawPath } from 'pixi-silk/path';
import { watch } from 'pixi-silk/signal';
import { PathGizmo } from 'pixi-silk/ui/pathgizmo';

declare const world: Container, overlay: Container, drop: BezierPath;

const history = new History();
const gizmo = new PathGizmo({ path: drop, space: world, history });
const shape = new SilkGraphics();

world.addChild(shape);
overlay.addChild(gizmo);
watch(gizmo.state.path, (path) => drawPath(shape.clear(), path).fill(0x0a84ff));

The gizmo does not draw the shape. It edits state.path, and the watch redraws the shape from it. Put the gizmo in an overlay and set space to the container the path lives in, usually the camera’s world. Anchors and handles then keep their size on screen at every zoom.

These are the gestures:

  • Click an anchor to select it, Shift + click to add more, and drag to move them all.
  • Drag a handle to bend the curve. Alt + drag breaks the symmetry.
  • Double-click an anchor to switch between a corner and a smooth point.
  • Click a segment to add an anchor there.
  • Delete or Backspace removes the selected anchors. Arrow keys nudge them, Shift by 10.
OptionDefaultWhat it does
spacethe gizmo’s parentThe container the path’s coordinates are in.
tool'edit''pen' starts with the pen.
historynoneRecords every edit as one undo step.
snapnoneMoves a dragged point, for a grid or smart guides.
keystrueDelete, Backspace, Escape, Enter and the arrow keys.
onChangenoneCalled on every change, drags included.
onCommitnoneCalled once per finished edit, with its label.

selectInRect(rect) selects the anchors inside a rectangle, the hook for a marquee. setClosed() closes or opens the path.

Draw with the pen#

Set state.tool to 'pen'. Click for a corner, or drag for a curve with mirrored handles. Alt during a drag moves only the outgoing handle. Click the last anchor to drop its outgoing handle, and click the first one to close the path. Escape or Enter stops drawing.

PenTool is the pen’s state machine with no pointer events and no drawing. Feed it presses, moves and releases to build paths from any input, such as a recorded gesture or a test.

Undo#

With a history, a whole drag, an inserted point, a pen click and a run of nudges are one step each. The gizmo needs only the HistoryLike interface, so your own store works too. See Gizmos and selection.

API reference: PathGizmo, PenTool, BezierPath, drawPath