a lightweight, headless drawbox-style canvas engine for indie sites and creative projects.
simple. fast. flexible.
Important
purrlet is in VERY early beta. expect bugs. expect things to break.
purrlet is a headless drawing engine.
you handle the ui. purrlet handles the drawing logic.
npm install purrletframework-specific subpath imports are also supported, so you can keep examples aligned with your stack:
import { Purrlet } from "purrlet/vue";
import { Purrlet } from "purrlet/sveltekit";
import { Purrlet } from "purrlet/astro";available aliases:
purrlet/reactpurrlet/nextpurrlet/vuepurrlet/nuxtpurrlet/sveltepurrlet/sveltekitpurrlet/astropurrlet/solidpurrlet/solidstartpurrlet/qwikpurrlet/remix
or via cdn:
<script type="module">
import { Purrlet } from "https://unpkg.com/purrlet/dist/purrlet.min.js";
</script>import { Purrlet } from "purrlet";
const canvas = document.getElementById("c");
const p = new Purrlet({
canvas,
debug: true
});
p.setTool("brush", {
color: "#000",
size: 5
});switch tools whenever:
p.setTool("brush", { color: "#000", size: 5 });
p.setTool("line", { color: "blue", size: 3 });
p.setTool("eraser", { size: 20 });
p.setTool("fill", { color: "gold", tolerance: 8 });
p.setTool("eyedropper", {
onPickColor: (color) => console.log(color)
});Note
the following tools exist: brush, line, eraser, fill, eyedropper
undo / redo:
p.undo();
p.redo();saving:
const p = new Purrlet({
canvas,
save: {
enabled: true,
key: "my-drawing",
strategy: "commands"
}
});
await p.save();save strategies:
save: {
enabled: true,
key: "my-drawing",
strategy: "commands" // default
}commands: stores replayable tool interactions inlocalStorage. best when the drawing is made through purrlet tools.blob: stores a PNGBlobinIndexedDB. use this when you also draw directly withctx, seed scenes manually, or need a full raster snapshot.data-url: legacy mode. stores base64 PNG data inlocalStorage. (NOT RECOMMENDED, obselete and will be removed in future versions)
await p.clearSave();purrlet supports imgbb and imgur out of the box. you can also plug in your own thing. anything.
// imgbb
const p = new Purrlet({
canvas,
upload: {
provider: "imgbb",
apiKey: "YOUR_API_KEY"
}
});
// imgur
const p = new Purrlet({
canvas,
upload: {
provider: "imgur",
apiKey: "YOUR_API_KEY"
}
});
const url = await p.upload();custom handler:
const p = new Purrlet({
canvas,
upload: {
handler: async (blob) => {
const res = await fetch("/upload", {
method: "POST",
body: blob
});
const { url } = await res.json();
return url;
}
}
});type PurrletConfig = {
canvas: HTMLCanvasElement;
debug?: boolean;
tool?: string;
save?: {
enabled?: boolean;
key?: string;
strategy?: "commands" | "blob" | "data-url";
maxCommands?: number;
};
upload?: {
provider?: "imgbb" | "imgur";
apiKey?: string;
clientId?: string;
handler?: (blob: Blob) => Promise<string>;
beforeUpload?: (blob: Blob) => Blob | Promise<Blob>;
onUploadSuccess?: (url: string) => void;
onUploadError?: (err: any) => void;
};
};if you are contributing through the github repo and wish to make a new tool / drawing tool:
start with:
npm run tool:new -- rectanglethat command will:
- create
src/tools/rectangle.ts - add a typed config block
- set the exported tool name
- register the tool in
src/tools/index.ts
then you only need to implement the tool logic.
the generated file looks like this:
import { defineTool } from "./defineTool";
import type { ToolInstance } from "./types";
type RectangleToolConfig = {
color?: string;
size?: number;
};
export const rectangleTool = defineTool({
name: "rectangle",
create(config: RectangleToolConfig = {}): ToolInstance {
return {
onDown(p, { ctx }) {
ctx.strokeStyle = config.color ?? "#000";
ctx.lineWidth = config.size ?? 4;
ctx.beginPath();
ctx.moveTo(p.x, p.y);
},
onMove(p, { ctx }) {
if (!p.isDown) return;
ctx.lineTo(p.x, p.y);
ctx.stroke();
},
onUp() {},
};
},
});after that:
- replace the starter logic with the actual tool behavior
- run
npm run build - add docs or tests if the tool needs them