Skip to main content

Canvas

Canvas is the ergonomic entry point. It accepts either a canvas element or a query selector, creates a Brush, configures the canvas for the current device pixel ratio or an explicit document size, and binds pointer events.

The Canvas API is part of the stable 1.0.0 surface and is intended for production-ready drawing experiences.

Canvas API behavior

Draw here to see pointer input, pressure simulation, undo, redo, clear, and eraser mode working through the Canvas wrapper.

Loading demo…
import { Canvas } from "fuderu";

const painter = new Canvas({
canvas: document.querySelector("canvas")!,
document: {
width: 1024,
height: 768,
},
pressureSimulation: true,
brush: {
color: "#222222",
size: 18,
roundness: 0.8,
},
});

Constructor Options

interface CanvasOptions {
canvas: HTMLCanvasElement | string;
/**
* The logical drawing resolution.
* This is the size of the internal pixel buffer, independent of how
* the canvas element is sized on screen via CSS.
* Defaults to the displayed canvas size multiplied by devicePixelRatio.
*/
document?: {
width: number;
height: number;
};
brush?: BrushConfig;
pressureSimulation?: boolean;
}

Options

OptionDescription
canvasThe canvas element or a CSS selector string
documentOptional logical drawing dimensions (width/height in pixels). If not provided, defaults to the canvas element's display size multiplied by window.devicePixelRatio
brushOptional brush configuration
pressureSimulationWhen true, mouse/touch input uses simulated pressure based on movement speed. Defaults to false.

Defaults

All options are optional except canvas. Here are the default values:

{
// canvas: required (no default)
document: undefined, // defaults to canvas size * devicePixelRatio
brush: {
size: 20,
opacity: 1,
flow: 1,
color: "#000000",
angle: 0,
roundness: 1,
spacing: 0.12,
eraser: false,
},
pressureSimulation: false,
}

Pressure

Real pen pressure is used when the pointer is a pen and event.pressure is available. Mouse and touch input can use simulated pressure:

const painter = new Canvas({
canvas: "#paint",
pressureSimulation: false,
});

Set pressureSimulation to true to enable simulated pressure based on movement speed for mouse and touch input.

lastPressure stores the most recent pressure sent to the brush, which is handy for UI meters without advancing the pressure simulator again.

Layer System

Fuderu includes a professional layer system that allows you to create, manage, and composite multiple layers similar to professional digital art software.

Layer Management

The Canvas instance includes a layers property that provides access to the layer management API:

// Create a new layer (this automatically sets it as active)
const layer = painter.createLayer({
name: "Line Art",
opacity: 0.8,
blendMode: "multiply",
});

// Get the active layer (which is now the new layer)
const activeLayer = painter.getActiveLayer();

// Set a layer as active manually using its ID
painter.setActiveLayer(layer.id);

// Delete a layer by ID
painter.deleteLayer("some-layer-id");

// Duplicate a layer
const duplicatedLayer = painter.duplicateLayer(layer.id);

// Get all layers
const allLayers = painter.getLayers();

// Update layer properties by ID
painter.updateLayer(layer.id, {
opacity: 0.5,
blendMode: "screen",
visible: true,
});

Blend Modes

Layers support standard Canvas 2D blend modes for advanced compositing effects:

  • source-over (default) - Normal layer stacking
  • multiply - Darkens the base layer
  • screen - Lightens the base layer
  • overlay - Combines multiply and screen
  • darken - Keeps the darker of the two layers
  • lighten - Keeps the lighter of the two layers
  • color-dodge - Brightens the base layer
  • color-burn - Darkens the base layer
  • hard-light - Intense overlay effect
  • soft-light - Soft overlay effect
  • difference - Subtracts the darker color from the lighter
  • exclusion - Similar to difference but lower contrast
  • hue - Applies the hue of the blend layer
  • saturation - Applies the saturation of the blend layer
  • color - Applies both hue and saturation
  • luminosity - Applies the luminosity of the blend layer

Example using blend modes for a multiply blending effect:

// Create a base color layer
const baseLayer = painter.createLayer({
name: "Base Color",
blendMode: "source-over",
});

// Draw your base colors on this layer
// ...

// Create a shading layer with multiply blend mode
const shadeLayer = painter.createLayer({
name: "Shading",
blendMode: "multiply",
});

// Draw shadows and shading on this layer
// The multiply blend mode will darken the colors below

Layer Properties

Each layer has the following properties:

  • id - Unique identifier for the layer
  • name - Human-readable name for the layer
  • visible - Whether the layer is visible (default: true)
  • opacity - Opacity of the layer (0-1, default: 1)
  • blendMode - How the layer composites with layers below it

Direct Layer Manager Access

For advanced layer operations, you can access the underlying `LayerManager` instance via the `layers` property on the Canvas:

// Move a layer to a specific index (0-based)
painter.layers.moveLayer(layerId, targetIndex);

// Resize all layers (called automatically when canvas size changes)
painter.layers.resize(width, height);

// Clear all layers
painter.layers.clear();

// Get readonly array of all layers
const allLayers = painter.layers.getAll();

API Reference

resize()

Resize the internal drawing buffer to match the element's display size. Also reinitialises the brush context so the brush's offscreen canvases match the new pixel buffer.

setDocumentSize(width, height)

Change the logical document size.

This clears all artwork and resets the undo stack. It does NOT change any CSS on the canvas element. Sizing the element on screen is the caller's responsibility.

undo(), redo(), clear()

Undo/redo the last user action or clear the canvas.

Starting in version 1.1.0, Canvas manages a unified history stack via HistoryManager. It supports undoing and redoing not just paint strokes, but also a variety of layer operations:

  • Drawing strokes (CanvasStateHistoryEntry)
  • Layer creations (LayerCreatedHistoryEntry)
  • Layer deletions (LayerDeletedHistoryEntry)
  • Layer reorderings (MoveLayerHistoryEntry)
  • Layer property changes such as name, visibility, opacity, and blend modes (LayerPropertyHistoryEntry)

Because history is managed at the high-level Canvas wrapper, switching the active layer no longer wipes the undo/redo stack. Users can switch layers freely and still undo/redo their strokes across layers seamlessly.

history

The HistoryManager instance on the canvas. It provides low-level control over the undo/redo stack:

// Check if undo or redo are available
const canUndo = painter.history.canUndo();
const canRedo = painter.history.canRedo();

// Clear the history stack manually
painter.history.clear();

setSmooth(enabled), setSpacing(enabled), setEraser(enabled)

Toggle brush smoothing, spacing, and eraser mode.

loadConfig(config)

Update brush configuration.

loadImage(img)

Load an image for use as an image brush.

destroy()

Clean up event listeners and references.

Document Persistence API

Fuderu 1.3.0 introduces native document persistence for saving and restoring complete canvas compositions.

exportDocument(): DocumentData

Serializes the entire canvas document state—including dimensions, version, active layer ID, and pixel data for every layer (as PNG data URLs)—into a structured JSON object:

const docData = painter.exportDocument();
// Save docData as JSON string to localStorage, server, or file

importDocument(data: DocumentData): void

Restores the complete canvas state from a serialized DocumentData object:

painter.importDocument(docData);

exportPNG(options?: { includeBackground?: boolean; backgroundColor?: string }): string

Renders a flattened composite image of all visible layers and returns a PNG Data URL:

// Export transparent PNG composite
const pngDataUrl = painter.exportPNG();

// Export with opaque background
const solidPng = painter.exportPNG({
includeBackground: true,
backgroundColor: "#ffffff",
});

Native Raster Commands

Fuderu provides built-in utilities for shape primitives, flood fill, text, and color sampling:

floodFill(x: number, y: number, color: string, tolerance?: number): void

Performs a fast scanline flood fill on the active layer starting at (x, y):

// Fill bounded region with blue (0-255 tolerance)
painter.floodFill(150, 200, "#2563eb", 10);

drawRectangle(x, y, width, height, options?): void

Draws a vector rectangle primitive onto the active layer:

painter.drawRectangle(50, 50, 200, 150, {
strokeColor: "#000000",
strokeWidth: 4,
fillColor: "#3b82f6",
filled: true,
});

drawEllipse(cx, cy, rx, ry, options?): void

Draws a vector ellipse primitive onto the active layer:

painter.drawEllipse(150, 150, 80, 50, {
strokeColor: "#ef4444",
strokeWidth: 2,
});

drawLine(x1, y1, x2, y2, options?): void

Draws a straight line onto the active layer:

painter.drawLine(0, 0, 400, 300, {
strokeColor: "#10b981",
strokeWidth: 6,
lineCap: "round",
});

drawText(text, x, y, options?): void

Renders text onto the active layer:

painter.drawText("Hello Fuderu!", 100, 100, {
fontSize: 32,
fontFamily: "Inter, sans-serif",
color: "#1e293b",
});

getColorAt(x: number, y: number, source?: "active" | "composite"): { r, g, b, a, hex }

Samples pixel color at document coordinate (x, y):

const sample = painter.getColorAt(200, 150, "composite");
console.log(sample.hex); // "#2563eb"

Event System & State Snapshots

Fuderu features a reactive event model for seamless state management and UI syncing:

on(event, handler) / off(event, handler)

Subscribe to typed canvas events (change, stroke:start, stroke:end, layer:change, history:change):

const unsubscribe = painter.on("change", () => {
console.log("Canvas state changed!");
});

getSnapshot(): CanvasSnapshot

Returns an immutable snapshot of canvas state suitable for React's useSyncExternalStore:

const snapshot = painter.getSnapshot();
console.log(snapshot.layerCount, snapshot.activeLayerId, snapshot.canUndo);

History Navigation API

Navigate history timelines directly or inject custom undo patches:

history.goTo(index: number): void

Jump directly to any point in the history timeline:

// Jump to step 3 in undo history
painter.history.goTo(3);

history.getStepSummaries(): HistoryStepSummary[]

Retrieve human-readable step summaries for building history timeline UI panels.