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
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:
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:
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
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:
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
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.
