react-whiteboard-sketch
v1.1.3
Published
🗾 Simple React whiteboard sketching hook
Maintainers
Readme
🗾 react-whiteboard-sketch
A simple, fully-typed React hook for adding whiteboard/canvas drawing functionality to your React applications.
Features
- Simple Hook-Based API - No components required, just use the hook
- Drawing with Strokes - Draw with customizable colors and widths
- Eraser Tool - Erase parts of your drawing with adjustable width
- Background Images - Add images as canvas backgrounds with aspect ratio control
- PNG Export - Export your drawings as PNG (including background)
- State Persistence - Save and load whiteboard state (localStorage, API, etc.)
- Read-Only Mode - Display completed drawings without editing
- Undo/Redo - Full history support for going back and forward
- Fully Typed - Written in TypeScript with complete type definitions
- Cross-Browser Compatible - Works with mouse and touch events
- Reactive - All changes trigger automatic re-renders
Installation
npm install react-whiteboard-sketchyarn add react-whiteboard-sketchpnpm add react-whiteboard-sketchQuick Start
import { useWhiteboard, Canvas } from 'react-whiteboard-sketch';
function App() {
const { canvasRef, clear, undo, redo, exportAsPNG } = useWhiteboard({
width: 800,
height: 600,
strokeColor: '#000000',
strokeWidth: 2,
backgroundColor: '#ffffff', // Default white background
});
const handleExport = () => {
const dataUrl = exportAsPNG();
// Exports with white background, not transparent
const link = document.createElement('a');
link.download = 'whiteboard.png';
link.href = dataUrl;
link.click();
};
return (
<div>
<div>
<button onClick={undo}>Undo</button>
<button onClick={redo}>Redo</button>
<button onClick={clear}>Clear</button>
<button onClick={handleExport}>Export PNG</button>
</div>
<Canvas
ref={canvasRef}
style={{
border: '1px solid #ccc',
borderRadius: '4px',
}}
/>
</div>
);
}Note: The canvas is automatically optimized for high-DPI displays (Retina, etc.) for sharp rendering.
API Reference
useWhiteboard(options?)
The main hook for creating a whiteboard instance.
Options
interface WhiteboardOptions {
/**
* Stroke color (CSS color string)
* @default "#000000"
*/
strokeColor?: string;
/**
* Stroke width in pixels
* @default 2
*/
strokeWidth?: number;
/**
* Background image URL or HTMLImageElement
*/
backgroundImage?: string | HTMLImageElement | null;
/**
* Canvas width in pixels
* @default 800
*/
width?: number;
/**
* Canvas height in pixels
* @default 600
*/
height?: number;
}Returns
interface WhiteboardResult {
/**
* Ref to attach to the canvas element
*/
canvasRef: RefObject<HTMLCanvasElement | null>;
/**
* Clear all strokes from the canvas
*/
clear: () => void;
/**
* Undo the last stroke
*/
undo: () => void;
/**
* Redo the previously undone stroke
*/
redo: () => void;
/**
* Export the canvas as PNG data URL
*/
exportAsPNG: () => string;
/**
* Set the stroke color
*/
setStrokeColor: (color: string) => void;
/**
* Set the stroke width
*/
setStrokeWidth: (width: number) => void;
/**
* Set the background image
*/
setBackgroundImage: (image: string | HTMLImageElement | null) => void;
/**
* Check if undo is available
*/
canUndo: boolean;
/**
* Check if redo is available
*/
canRedo: boolean;
}Advanced Examples
With Dynamic Controls
import { useState } from 'react';
import { useWhiteboard } from 'react-whiteboard-sketch';
function WhiteboardApp() {
const [color, setColor] = useState('#000000');
const [width, setWidth] = useState(5);
const {
canvasRef,
clear,
undo,
redo,
setStrokeColor,
setStrokeWidth,
canUndo,
canRedo,
} = useWhiteboard({
width: 1000,
height: 700,
strokeColor: color,
strokeWidth: width,
});
const handleColorChange = (newColor: string) => {
setColor(newColor);
setStrokeColor(newColor);
};
const handleWidthChange = (newWidth: number) => {
setWidth(newWidth);
setStrokeWidth(newWidth);
};
return (
<div>
<div>
<label>Color: </label>
<input
type="color"
value={color}
onChange={(e) => handleColorChange(e.target.value)}
/>
<label>Width: {width}px</label>
<input
type="range"
min="1"
max="50"
value={width}
onChange={(e) => handleWidthChange(Number(e.target.value))}
/>
</div>
<div>
<button onClick={undo} disabled={!canUndo}>Undo</button>
<button onClick={redo} disabled={!canRedo}>Redo</button>
<button onClick={clear}>Clear</button>
</div>
<canvas
ref={canvasRef}
style={{
border: '1px solid #ccc',
cursor: 'crosshair',
touchAction: 'none',
}}
/>
</div>
);
}With Background Image
import { useWhiteboard, Canvas } from 'react-whiteboard-sketch';
function WhiteboardWithBackground() {
const {
canvasRef,
setBackgroundImage,
exportAsPNG,
} = useWhiteboard({
width: 800,
height: 600,
backgroundImage: 'https://example.com/background.jpg',
// Background images always preserve aspect ratio (default: 'contain')
preserveBackgroundImageAspectRatio: 'contain', // or 'cover', 'fill', 'none', 'scale-down'
});
const handleExport = () => {
const dataUrl = exportAsPNG();
// The exported PNG includes the background image
const link = document.createElement('a');
link.download = 'whiteboard-with-background.png';
link.href = dataUrl;
link.click();
};
return (
<div>
<input
type="text"
placeholder="Background URL"
onChange={(e) => setBackgroundImage(e.target.value)}
/>
<button onClick={handleExport}>Export with Background</button>
<Canvas ref={canvasRef} />
</div>
);
}With Transparent Background
import { useWhiteboard, Canvas } from 'react-whiteboard-sketch';
function TransparentWhiteboard() {
const { canvasRef, exportAsPNG } = useWhiteboard({
width: 800,
height: 600,
backgroundColor: 'transparent', // For transparent PNG export
});
return <Canvas ref={canvasRef} />;
}With Eraser Tool
import { useWhiteboard } from 'react-whiteboard-sketch';
function WhiteboardWithEraser() {
const {
canvasRef,
mode,
setMode,
eraserWidth,
setEraserWidth,
} = useWhiteboard({
width: 800,
height: 600,
eraserWidth: 20,
});
return (
<div>
<button onClick={() => setMode('draw')}>Draw</button>
<button onClick={() => setMode('erase')}>Erase</button>
<span>Mode: {mode}</span>
{mode === 'erase' && (
<input
type="range"
min="5"
max="100"
value={eraserWidth}
onChange={(e) => setEraserWidth(Number(e.target.value))}
/>
)}
<canvas ref={canvasRef} />
</div>
);
}With State Persistence (LocalStorage)
import { useState, useEffect } from 'react';
import { useWhiteboard, type WhiteboardState } from 'react-whiteboard-sketch';
function PersistentWhiteboard() {
const STORAGE_KEY = 'my-whiteboard';
// Load initial state from localStorage
const [initialState] = useState<WhiteboardState | undefined>(() => {
const saved = localStorage.getItem(STORAGE_KEY);
return saved ? JSON.parse(saved) : undefined;
});
const {
canvasRef,
getState,
loadState,
clear,
} = useWhiteboard({
width: 800,
height: 600,
initialState,
});
// Auto-save every second
useEffect(() => {
const interval = setInterval(() => {
const state = getState();
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
}, 1000);
return () => clearInterval(interval);
}, [getState]);
const handleClear = () => {
clear();
localStorage.removeItem(STORAGE_KEY);
};
return (
<div>
<button onClick={handleClear}>Clear All</button>
<canvas ref={canvasRef} />
</div>
);
}With API Integration
import { useWhiteboard, type WhiteboardState } from 'react-whiteboard-sketch';
function APIWhiteboard() {
const { canvasRef, getState, loadState } = useWhiteboard({
width: 800,
height: 600,
});
const handleSave = async () => {
const state = getState();
await fetch('/api/whiteboard/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(state),
});
};
const handleLoad = async () => {
const response = await fetch('/api/whiteboard/load');
const state: WhiteboardState = await response.json();
loadState(state);
};
return (
<div>
<button onClick={handleSave}>Save to API</button>
<button onClick={handleLoad}>Load from API</button>
<canvas ref={canvasRef} />
</div>
);
}Read-Only Mode
import { useWhiteboard, type WhiteboardState } from 'react-whiteboard-sketch';
function ReadOnlyWhiteboard({ savedState }: { savedState: WhiteboardState }) {
const { canvasRef } = useWhiteboard({
width: 800,
height: 600,
initialState: savedState,
readOnly: true, // Disable all drawing interactions
});
return (
<div>
<p>This whiteboard is in read-only mode</p>
<canvas ref={canvasRef} style={{ cursor: 'default' }} />
</div>
);
}TypeScript
This package is written in TypeScript and includes all type definitions. No need for additional @types packages.
import type {
WhiteboardOptions,
WhiteboardResult,
WhiteboardState,
Stroke,
StrokePoint,
DrawingMode,
} from 'react-whiteboard-sketch';Browser Support
Works in all modern browsers that support:
- HTML5 Canvas
- React 19+
- ES6+
Tested on:
- Chrome/Edge (latest)
- Firefox (latest)
- Safari (latest)
- Mobile browsers (iOS Safari, Chrome Mobile)
License
MIT © Conner Bachmann
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
