npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fartinmartin/canvas-paint

v1.1.0

Published

``` npm i @fartinmartin/canvas-paint ```

Readme

canvas-paint

Install

npm i @fartinmartin/canvas-paint

Usage

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 listeners

Properties

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 drawn

Events

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.