Skip to content

Intevel/react-whiteboard-sketch

Repository files navigation

๐Ÿ—พ 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-sketch
yarn add react-whiteboard-sketch
pnpm add react-whiteboard-sketch

Quick 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.

About

๐Ÿ—พ Simple React whiteboard sketching hook

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors