Canvas API
Draw graphics and generate images with the HTML Canvas 2D API and node-canvas.
You are an expert in the Canvas 2D rendering context, both in the browser and on the server via `@napi-rs/canvas` or `canvas` (node-canvas). You create dynamic image generators, chart renderers, and pixel-manipulation utilities with precise control over drawing operations.
## Key Points
- **Forgetting `beginPath()` before new shapes** -- without it, `stroke()` redraws every previous path segment, causing visual artifacts and performance degradation.
- **Using Canvas for static SVG-like graphics in the browser** -- SVG is resolution-independent and accessible; Canvas is for dynamic or pixel-level work.
- **Reading `getImageData` in a hot loop** -- each call copies pixel data from GPU to CPU; batch reads and minimize calls.
- **Not handling DPI scaling** -- on HiDPI displays, set `canvas.width = displayWidth * devicePixelRatio` and scale the context, or output looks blurry.
- Generating dynamic OG images or social cards on the server at request time.
- Building custom chart or data visualization components beyond what charting libraries offer.
- Applying real-time image filters or overlays in the browser (e.g., photo editor).
- Creating certificate, badge, or label generators in a Node.js service.
- Rendering game graphics or interactive visualizations that need per-pixel control.
## Quick Example
```typescript
ctx.fillStyle = "red";
ctx.globalAlpha = 0.5;
drawHeader(ctx); // inherits red + 0.5 alpha -- unintended
drawFooter(ctx); // also inherits -- bugs compound
```
```typescript
function draw(ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ... draw frame
requestAnimationFrame(() => draw(ctx, canvas));
}
```skilldb get design-tool-services-skills/Canvas APIFull skill: 220 linesHTML Canvas API & node-canvas
You are an expert in the Canvas 2D rendering context, both in the browser and on the server via @napi-rs/canvas or canvas (node-canvas). You create dynamic image generators, chart renderers, and pixel-manipulation utilities with precise control over drawing operations.
Core Philosophy
Immediate Mode Rendering
Canvas is an immediate-mode API: draw calls paint pixels onto a bitmap and are not retained as objects. Plan your rendering order carefully -- there is no layer reordering after the fact.
State Machine Discipline
The 2D context is a state machine. Every fillStyle, transform, or globalAlpha change persists until reset. Use save()/restore() religiously to avoid state leakage between drawing operations.
Server-Side Generation
Use @napi-rs/canvas (faster, no native deps on most platforms) or canvas (node-canvas, Cairo-based) to generate images in Node.js -- OG images, charts, certificates -- without a browser.
Setup
# Server-side (pick one)
npm install @napi-rs/canvas # Rust-based, prebuilt binaries
npm install canvas # C++-based, needs build tools
# Types for browser Canvas API
npm install -D @types/dom-webcodecs
// Server-side with @napi-rs/canvas
import { createCanvas, GlobalFonts } from "@napi-rs/canvas";
GlobalFonts.registerFromPath("./fonts/Inter-Bold.ttf", "Inter");
const canvas = createCanvas(1200, 630);
const ctx = canvas.getContext("2d");
Key Patterns
Do: Use save()/restore() around state changes
ctx.save();
ctx.fillStyle = "#6366f1";
ctx.globalAlpha = 0.8;
ctx.fillRect(0, 0, 200, 200);
ctx.restore();
// fillStyle and globalAlpha are back to previous values
Not: Mutate state without restoring
ctx.fillStyle = "red";
ctx.globalAlpha = 0.5;
drawHeader(ctx); // inherits red + 0.5 alpha -- unintended
drawFooter(ctx); // also inherits -- bugs compound
Do: Clear before redraw in animation loops
function draw(ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ... draw frame
requestAnimationFrame(() => draw(ctx, canvas));
}
Common Patterns
Generate OG image on the server
import { createCanvas, GlobalFonts, loadImage } from "@napi-rs/canvas";
async function generateOGImage(title: string, author: string): Promise<Buffer> {
const canvas = createCanvas(1200, 630);
const ctx = canvas.getContext("2d");
// Background gradient
const grad = ctx.createLinearGradient(0, 0, 1200, 630);
grad.addColorStop(0, "#1e1b4b");
grad.addColorStop(1, "#312e81");
ctx.fillStyle = grad;
ctx.fillRect(0, 0, 1200, 630);
// Title
ctx.fillStyle = "#ffffff";
ctx.font = 'bold 56px "Inter"';
wrapText(ctx, title, 80, 200, 1040, 68);
// Author
ctx.fillStyle = "#a5b4fc";
ctx.font = '28px "Inter"';
ctx.fillText(author, 80, 520);
return canvas.toBuffer("image/png");
}
function wrapText(
ctx: CanvasRenderingContext2D,
text: string,
x: number, y: number,
maxWidth: number, lineHeight: number
) {
const words = text.split(" ");
let line = "";
let currentY = y;
for (const word of words) {
const test = line + word + " ";
if (ctx.measureText(test).width > maxWidth && line) {
ctx.fillText(line.trim(), x, currentY);
line = word + " ";
currentY += lineHeight;
} else {
line = test;
}
}
ctx.fillText(line.trim(), x, currentY);
}
Draw a bar chart
interface BarData { label: string; value: number; color: string }
function drawBarChart(
ctx: CanvasRenderingContext2D,
data: BarData[],
x: number, y: number,
width: number, height: number
) {
const maxVal = Math.max(...data.map((d) => d.value));
const barWidth = width / data.length - 10;
data.forEach((d, i) => {
const barHeight = (d.value / maxVal) * height;
const bx = x + i * (barWidth + 10);
const by = y + height - barHeight;
ctx.save();
ctx.fillStyle = d.color;
ctx.beginPath();
ctx.roundRect(bx, by, barWidth, barHeight, [4, 4, 0, 0]);
ctx.fill();
ctx.fillStyle = "#64748b";
ctx.font = "14px sans-serif";
ctx.textAlign = "center";
ctx.fillText(d.label, bx + barWidth / 2, y + height + 18);
ctx.restore();
});
}
Pixel manipulation for image filters
function grayscale(ctx: CanvasRenderingContext2D, w: number, h: number) {
const imageData = ctx.getImageData(0, 0, w, h);
const { data } = imageData;
for (let i = 0; i < data.length; i += 4) {
const avg = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
data[i] = data[i + 1] = data[i + 2] = avg;
}
ctx.putImageData(imageData, 0, 0);
}
Offscreen canvas for web workers
// main.ts
const offscreen = document.querySelector("canvas")!.transferControlToOffscreen();
const worker = new Worker("render-worker.ts", { type: "module" });
worker.postMessage({ canvas: offscreen }, [offscreen]);
// render-worker.ts
self.onmessage = (e: MessageEvent<{ canvas: OffscreenCanvas }>) => {
const ctx = e.data.canvas.getContext("2d")!;
ctx.fillStyle = "#6366f1";
ctx.fillRect(0, 0, 400, 400);
};
Composite multiple images
import { createCanvas, loadImage } from "@napi-rs/canvas";
async function compositeImages(paths: string[], outputWidth: number) {
const images = await Promise.all(paths.map(loadImage));
const tileSize = Math.floor(outputWidth / images.length);
const canvas = createCanvas(outputWidth, tileSize);
const ctx = canvas.getContext("2d");
images.forEach((img, i) => {
ctx.drawImage(img, i * tileSize, 0, tileSize, tileSize);
});
return canvas.toBuffer("image/png");
}
Anti-Patterns
- Forgetting
beginPath()before new shapes -- without it,stroke()redraws every previous path segment, causing visual artifacts and performance degradation. - Using Canvas for static SVG-like graphics in the browser -- SVG is resolution-independent and accessible; Canvas is for dynamic or pixel-level work.
- Reading
getImageDatain a hot loop -- each call copies pixel data from GPU to CPU; batch reads and minimize calls. - Not handling DPI scaling -- on HiDPI displays, set
canvas.width = displayWidth * devicePixelRatioand scale the context, or output looks blurry.
When to Use
- Generating dynamic OG images or social cards on the server at request time.
- Building custom chart or data visualization components beyond what charting libraries offer.
- Applying real-time image filters or overlays in the browser (e.g., photo editor).
- Creating certificate, badge, or label generators in a Node.js service.
- Rendering game graphics or interactive visualizations that need per-pixel control.
Install this skill directly: skilldb add design-tool-services-skills
Related Skills
Chromatic
Automate visual regression testing with Chromatic. Configure snapshot capture,
Design Tokens
Manage cross-platform design tokens with Style Dictionary. Define token schemas,
Figma API
Integrate with the Figma REST API and webhooks to read design files, extract components,
Iconify
Integrate icons at scale with the Iconify framework. Use framework-specific
Sharp
Process images at high performance with Sharp. Resize, crop, convert formats,
Storybook
Develop and test UI components in isolation with Storybook. Configure stories,