SketchFlow

A freeform canvas drawing and whiteboard application with hand-drawn aesthetics

RoleLead Developer
TypeCreative Tool
StackReact / Canvas API / Rough.js
StatusActive
SketchFlow application screenshot
01Overview

SketchFlow is a browser-based drawing tool that combines the charm of hand-drawn illustrations with the precision of digital tools. Inspired by Excalidraw, the goal was to build a whiteboard experience that feels natural and organic rather than sterile and mechanical.

The application uses Rough.js to render shapes with a sketchy, hand-drawn quality, and perfect-freehand to capture pressure-sensitive strokes from pen and touch input. The result is a drawing tool where even simple rectangles and arrows look like they were drawn by hand on paper.

Everything runs client-side in the browser using the Canvas API. There's no server component — drawings can be exported to PNG for sharing. The architecture is designed around a Zustand store with discriminated union types, making state management predictable and type-safe.

02Key Features
Hand-Drawn Shapes
Rough.js renders rectangles, ellipses, lines, and arrows with a natural hand-drawn style that gives diagrams personality.
Pressure-Sensitive Drawing
perfect-freehand converts pointer input into smooth, variable-width strokes that respond to pen pressure and velocity.
Undo / Redo
Full history stack with keyboard shortcuts. Every action is reversible, supporting both element creation and property changes.
PNG Export
Export the canvas to a high-resolution PNG file with transparent or white background options for easy sharing.
Tool Palette
Selection, freehand, rectangle, ellipse, line, arrow, and eraser tools with customizable stroke color and width.
Infinite Canvas
Pan and zoom across an unbounded canvas with smooth transitions, supporting both mouse wheel and trackpad gestures.
03Technical Architecture

The rendering pipeline has two layers: a static canvas for committed elements and a dynamic canvas for the element currently being drawn. This dual-canvas approach means only the active stroke needs per-frame rendering, while the background canvas is only repainted when the element list changes.

Element state is managed through a Zustand store organized into slices, with elements stored in a Map keyed by ID for O(1) lookups. Each element uses a discriminated union type where the tool field determines which properties are available, making it impossible to access shape-specific data on a freehand element, or vice versa. Immer handles immutable updates inside the store, keeping mutation logic readable.

Pointer events are normalized across mouse, touch, and pen inputs using the Pointer Events API. For freehand strokes, raw pointer data is fed through perfect-freehand's algorithm, which outputs SVG path data rendered onto the canvas. Shape elements use Rough.js's generator to produce randomized path data that mimics hand drawing.

04Tech Stack
React 19UI framework
TypeScript 5.7Type safety
Vite 6Build tool
ZustandState management
immerImmutable updates
CSS ModulesScoped styling
Rough.jsHand-drawn rendering
perfect-freehandStroke smoothing
Canvas API2D rendering
Pointer EventsInput handling