Skip to content

Rough.js

Rough.js renders sketchy primitives and SVG paths to Canvas or SVG. Rougher Stuff extends its familiar drawing API with rounded shapes, semantic drawables, render plans, retained updates, and progress-driven animation.

Surface APIs

ts
import rough from '@rougher-stuff/roughjs';

const canvas = rough.canvas(document.querySelector('canvas')!);
canvas.circle(80, 80, 64, { fill: '#cc5533', seed: 7 });

const svg = document.querySelector('svg')!;
const drawing = rough.svg(svg);
svg.append(drawing.line(10, 10, 180, 80));

Focused entry points are available from @rougher-stuff/roughjs/canvas, @rougher-stuff/roughjs/svg, @rougher-stuff/roughjs/generator, and @rougher-stuff/roughjs/core.

Instrument styles

Use a preset when a drawing should feel like one consistent writing instrument instead of combining roughness, bowing, stroke width, and fill behavior by hand:

ts
import rough from '@rougher-stuff/roughjs';
import { createRoughStyle } from '@rougher-stuff/roughjs/style';

const style = createRoughStyle({
  preset: 'ballpoint',
  seed: 42,
  stroke: '#174a7e',
});

rough.canvas(document.querySelector('canvas')!).line(20, 20, 220, 100, style);

The built-in instruments are pencil, mechanical-pencil, ballpoint, felt-tip, marker, chalk, crayon, brush-pen, technical-pen, and dry-marker. Their normalized stroke personalities model width and pressure variation, edge and path noise, gaps, ink bleed, overshoot, and retracing. Traits and ordinary Rough.js options can both be overridden:

ts
const wornMarker = createRoughStyle({
  preset: 'dry-marker',
  personality: { gapProbability: 0.5, edgeNoise: 0.65 },
  strokeWidth: 5,
});

Explicit Rough.js options win over projected personality values. The helper is also available as rough.createRoughStyle(). The focused @rougher-stuff/roughjs/style entry point exports ROUGH_STYLE_PRESETS and the associated types.

Rounded geometry

ts
const generator = rough.generator();

generator.roundedRectangle(10, 10, 200, 100, {
  cornerRadius: { topLeft: 16, topRight: 4, bottomRight: 20, bottomLeft: 8 },
});

generator.roundedPolygon(points, {
  cornerRadius: [4, 8, 12, 8],
  cornerSmoothing: 0.35,
});

Oversized radii are clamped to adjacent edge lengths.

Semantic drawables

Use @rougher-stuff/roughjs/semantic to keep source geometry separate from compiled rough operations:

ts
import { compileDrawable, createDrawable } from '@rougher-stuff/roughjs/semantic';

const source = createDrawable(
  { type: 'rectangle', x: 20, y: 20, width: 160, height: 80, radius: 16 },
  {
    seed: 42,
    stroke: { width: 4, paint: { type: 'solid', color: '#191914' } },
  },
);

const compiled = compileDrawable(source);

The compiler identifies paint, transform, rough-geometry, and nominal-geometry changes so retained surfaces can reuse safe work.

Retained rendering

ts
import { svgSurface } from '@rougher-stuff/roughjs/retained';

const surface = svgSurface(svg);
const node = surface.mount(source);

node.update({ transform: { a: 1, b: 0, c: 0, d: 1, e: 100, f: 30 } });

const animation = node.animate({ autoplay: false, duration: 800 });
animation.seek(0.4).play();
await animation.finished;

See the package README for fill styles and lower-level entry points.

Released under the MIT License.