@fartinmartin/canvas-paint
v1.1.0
Published
``` npm i @fartinmartin/canvas-paint ```
Readme
canvas-paint
Install
npm i @fartinmartin/canvas-paintUsage
import { Paint } from "@fartinmartin/canvas-paint";
const root = document.getElementById("root");
const paint = new Paint(root, { width: 200, height: 200 });Options
new Paint(root, {
// Canvas
width: 200, // document width in px
height: 200, // document height in px
bgColor: undefined, // CSS color string; sets background of root element
margin: 0, // drawable area beyond document boundary (in document units)
debounce: 500, // ms delay for ResizeObserver
// Brush
brush: {
size: 5, // stroke width
color: "#ffaa00", // CSS color string
mode: "draw", // "draw" | "erase" | "fill"
cap: "round", // "butt" | "round" | "square"
join: "round", // "round" | "bevel" | "miter"
tolerance: 30, // point simplification tolerance
},
// Lazy brush
lazy: {
radius: 0, // lazy radius in px (0 = disabled)
enabled: true,
lerpFactor: 1, // multiplier on how far you must draw before reaching full radius
},
// UI overlay (brush preview, pointer, chain)
ui: {
show: true,
brush: true, // show brush size/shape preview at brush position
pointer: {
show: true,
size: 8,
color: "#000000",
},
center: {
show: true,
size: 4,
color: "#000000",
},
chain: {
show: false, // catenary chain between pointer and brush (requires lazy.radius > 0)
dash: [2, 4],
width: 2,
color: "#000000",
},
},
// Grid / boundary overlay
grid: {
show: true,
lines: {
show: false, // grid lines (default off)
size: 50, // spacing in document units
color: "rgba(0, 0, 0, 0.15)",
},
boundary: {
show: true, // document boundary rect (default on when margin > 0)
dash: [4, 4],
width: 1,
color: "rgba(0, 0, 0, 0.2)",
},
},
});Brush settings
Brush settings can be read and written directly at any time:
paint.brush.size = 10;
paint.brush.color = "red";
paint.brush.mode = "erase";
paint.brush.cap = "square";
paint.brush.join = "bevel";
paint.brush.tolerance = 20;
paint.brush.radius = 40; // lazy radius
paint.brush.lerpFactor = 2;Methods
paint.undo()
paint.redo()
paint.clear() // clears canvas and adds a clear entry to history
paint.reset() // clears canvas and wipes history (no undo)
paint.destroy()
paint.setBgColor(color) // update background color immediately
paint.setSize(width, height) // resize the document
paint.setMargin(margin) // update margin
// Playback options: { delay?: number (ms per point) } or { duration?: number (total ms) }
// If both are given, duration takes precedence and delay is ignored.
paint.drawHistory(options?) // redraws all paths; animates if delay/duration given
paint.save() // returns a serializable SaveData object
await paint.load(data, options?) // clears and replays a saved SaveData
await paint.toBlob(type?, quality?) // exports document area as Blob
await paint.toVideo(options?) // records drawHistory to a webm Blob; accepts fps too
paint.removeListeners() // detach all input event listenersProperties
paint.history.canUndo // boolean
paint.history.canRedo // boolean
paint.scale // current CSS px per document unit
paint.aspectRatio // width / height (including margin)
paint.path // the Path currently being drawnEvents
Subscribe via paint.events.on(event, handler). Handlers are fully typed when using TypeScript.
| event | payload | description |
| -------------- | --------------- | ------------------------------------------------- |
| start | BrushPayload | pointer down, drawing began |
| draw | BrushPayload | pointer move while drawing |
| end | BrushPayload | pointer up, path committed |
| leave | BrushPayload | pointer left canvas while drawing, path committed |
| resizing | — | ResizeObserver fired, resize in progress |
| resized | — | resize and history redraw complete |
| drawing | — | drawHistory started |
| drawingPath | — | each path committed during animated playback |
| drawn | — | drawHistory complete |
Brush-level events are also available via paint.brush.events.on(event, handler):
| event | payload | description |
| -------------- | --------------- | ------------------------ |
| brushUpdate | BrushPayload | any brush setting changed |
| move | BrushPayload | pointer moved |
| down | BrushPayload | pointer down |
| up | BrushPayload | pointer up |
| leave | BrushPayload | pointer left canvas |
TypeScript
Key types exported from the package:
import type {
PaintOptions,
PaintEvents,
BrushEvents,
BrushPayload,
PlaybackOptions,
SaveData, // shape returned by paint.save()
RenderData, // input shape for render()
VideoOptions,
} from "@fartinmartin/canvas-paint";Server-side rendering
render() and renderVideo() work in Node.js via node-canvas. Import from the /render entry point to avoid browser-only DOM dependencies.
Image:
import { render } from "@fartinmartin/canvas-paint/render";
import { createCanvas } from "canvas";
const canvas = render(data, { createCanvas });
canvas.toBuffer("image/png");Video — requires a custom encoder since captureStream and MediaRecorder are browser-only. The library ships no ffmpeg dependency; bring your own:
import { renderVideo } from "@fartinmartin/canvas-paint/render";
import { createCanvas } from "canvas";
const blob = await renderVideo(data, {
createCanvas,
encode: myFfmpegEncoder, // (opts: VideoEncodeOpts) => { start(): void; stop(): Promise<Blob> }
fps: 30,
delay: 10, // ms per point
duration: 3000, // total ms (converted to per-point delay); takes precedence over delay
});The encode function receives VideoEncodeOpts (artboard, temp, crop, width, height, bgColor, fps, mkCanvas) and must return { start(): void; stop(): Promise<Blob> } — the same interface as the browser default.
