Whiteboard drawing application
A tailored React Whiteboard component that supports drawing, shapes, text, images, and more. It comes with built-in state management and a customizable toolbar.
- Drawing Tools: Pencil, Text, Rectangle, Circle, Triangle, Diamond, Star, Heart, Hexagon, Octagon, Arrow.
- Image Support: Insert and manipulate images on the canvas.
- Canvas Controls: Pan and Zoom functionality.
- Grid System: Toggleable background grid.
- Selection: Select, move, and modify elements.
- Export: Export the whiteboard content as a PNG image.
- Locking Mechanism: Set the whiteboard to read-only mode with custom messages.
- Action Handling: Hook into internal actions for external syncing or logging.
Install the package and its peer dependencies:
``bash`
npm install whiteboard react react-dom
> Note: If this package is not yet published to npm, you can install it locally or link it.
Import the Whiteboard component and the stylesheet. The component takes up 100% of the parent container's width and height.
`jsx
import React from 'react';
import Whiteboard from 'whiteboard';
import 'whiteboard/style.css'; // Import styles
function App() {
return (
export default App;
`
You can control the whiteboard state and handle events using props and a ref.
`jsx
import React, { useRef } from 'react';
import Whiteboard from 'whiteboard';
import 'whiteboard/style.css';
function App() {
const whiteboardRef = useRef(null);
const handleAction = (action) => {
console.log('Action occurred:', action);
// Send action to server for real-time collaboration
};
const handleLockChange = (isLocked) => {
console.log('Lock state changed:', isLocked);
};
return (
// Example: read & replace elements
// const elements = whiteboardRef.current?.getElements();
// whiteboardRef.current?.setElements(elements);
// Example: clear all elements
// whiteboardRef.current?.clearElements();
`
You can programmatically apply actions to the whiteboard using the applyAction method exposed via the ref. This is essential for implementing real-time collaboration where you need to apply actions received from a server.
`jsx
import React, { useEffect, useRef } from 'react';
import Whiteboard from 'whiteboard';
function App() {
const whiteboardRef = useRef(null);
useEffect(() => {
// Example: mimicking receiving an action from a websocket
const mockIncomingAction = {
type: 'whiteboard/addElement',
payload: {
id: 'remote-1',
type: 'rectangle',
x: 200,
y: 200,
width: 100,
height: 100,
fillColor: '#FF0000'
}
};
// Apply the action to the whiteboard
if (whiteboardRef.current) {
// In a real app, this would be inside a socket event listener
setTimeout(() => {
whiteboardRef.current.applyAction(mockIncomingAction);
}, 1000);
}
}, []);
return (
API Reference
$3
| Prop | Type | Default | Description |
|------|------|---------|-------------|
|
onAction | (action: object) => void | undefined | Callback fired for content-changing actions (e.g., add/remove/move elements, text edits). It does not emit high-frequency UI actions like pan/zoom/selection/tool changes. |
| isLocked | boolean | false | When true, disables all editing tools and shows a lock indicator. |
| lockText | string | undefined | Custom text to display on the lock indicator when isLocked is true. |
| onLockChange | (isLocked: boolean) => void | undefined | Callback fired when the lock state changes. |
| onImageUpload | (file: File) => Promise | undefined | Async callback to handle image uploads. Must return a Promise that resolves to the image URL. |$3
By default, the whiteboard creates temporary Blob URLs for uploaded images. This works great for local sessions but won't work for real-time collaboration because Blob URLs are local to the browser tab.
To support multiplayer image sharing, you must provide the
onImageUpload prop. This function should upload the file to your server (e.g., S3, Cloudinary) and return the public URL.`jsx
const handleImageUpload = async (file) => {
// 1. Upload file to your server
const formData = new FormData();
formData.append('file', file);
const response = await fetch('https://your-api.com/upload', {
method: 'POST',
body: formData
});
const data = await response.json();
// 2. Return the public URL
return data.url;
};
`$3
You can access these methods by passing a
ref to the Whiteboard component.| Method | Arguments | Description |
|--------|-----------|-------------|
|
applyAction | (action: object) | Dispatches an action to the internal Redux store. Useful for applying remote updates. |
| getElems / getElements | () | Returns the current elements array from the internal store. |
| setElems / setElements | (elements: object[]) | Replaces the entire elements array in the internal store. |
| clearElems / clearElements | () | Clears all elements from the internal store. |
| canvas | - | Returns the underlying HTMLCanvasElement. |$3
For convenience (e.g., building your own
applyAction payloads), the package exports the internal Redux action creators:`js
import { whiteboardActions } from 'whiteboard';// Example: build an action object to send/apply
const action = whiteboardActions.clearElements();
// action.type === 'whiteboard/clearElements'
`#### All available actions
| Action creator |
action.type | Payload |
|---|---|---|
| toggleGrid() | whiteboard/toggleGrid | _none_ |
| setGridSize(size) | whiteboard/setGridSize | number |
| setGridColor(color) | whiteboard/setGridColor | string |
| setSelectedTool(tool) | whiteboard/setSelectedTool | string |
| setToolProperty({ tool, property, value }) | whiteboard/setToolProperty | { tool: string, property: string, value: any } |
| addElement(element) | whiteboard/addElement | Element (see “addElement payload” below) |
| updateElement({ id, updates }) | whiteboard/updateElement | { id: string, updates: object } |
| removeElement(id) | whiteboard/removeElement | string |
| setSelectedElement(idOrNull) | whiteboard/setSelectedElement | string \| null |
| clearElements() | whiteboard/clearElements | _none_ |
| setElements(elements) | whiteboard/setElements | object[] |
| setPan({ x, y }) | whiteboard/setPan | { x: number, y: number } |
| setZoom(zoom) | whiteboard/setZoom | number |
| resetViewport() | whiteboard/resetViewport | _none_ |
| fitToView({ canvasWidth, canvasHeight, padding? }) | whiteboard/fitToView | { canvasWidth: number, canvasHeight: number, padding?: number } |
| bringToFront(id) | whiteboard/bringToFront | string |
| sendToBack(id) | whiteboard/sendToBack | string |
| setLocked(isLocked) | whiteboard/setLocked | boolean |
| setWatermarkEnabled(visible) | whiteboard/setWatermarkEnabled | boolean |
| setWatermarkText(text) | whiteboard/setWatermarkText | string |####
addElement(element) payloadaddElement expects a full element object. Coordinates are world coordinates (the board handles pan/zoom internally).Common fields (all element types):
| Field | Type | Notes |
|---|---|---|
|
id | string | Must be unique (commonly Date.now().toString()). |
| type | string | One of the supported element types below. |Element schemas by
type:- rectangle / triangle / diamond / hexagon / octagon
- Required:
x: number, y: number, width: number, height: number
- Styling: strokeColor: string, fillColor: string, lineWidth: number- circle
- Required:
centerX: number, centerY: number, radius: number
- Styling: strokeColor: string, fillColor: string, lineWidth: number- pencil
- Required:
points: Array<{ x: number, y: number }>
- Styling: strokeColor: string, lineWidth: number- star / heart
- Required:
centerX: number, centerY: number, size: number
- Styling: strokeColor: string, fillColor: string, lineWidth: number- arrow
- Required:
startX: number, startY: number, endX: number, endY: number
- Styling: strokeColor: string, fillColor: string, lineWidth: number- text
- Required:
x: number, y: number, text: string
- Styling: fontSize: number, fontColor: string- image
- Required:
x: number, y: number, width: number, height: number, src: string
- Notes: src is typically a Data URL (from FileReader.readAsDataURL).Example (rectangle):
`js
import { whiteboardActions } from 'whiteboard';const action = whiteboardActions.addElement({
id: '1',
type: 'rectangle',
x: 100,
y: 80,
width: 200,
height: 120,
strokeColor: '#000000',
fillColor: '#ffffff',
lineWidth: 2,
});
``MIT