npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@savio99/react-draw

v1.2.6

Published

Simple responsive draw component to sign and draw in your own website

Readme

@savio99/react-draw

NPM Version License

A powerful React whiteboard/drawing library with support for freehand drawing, images, pan & zoom, grid backgrounds, floating toolbox, and more.

Features

  • ✏️ Freehand Drawing - Smooth pen strokes with customizable color and width
  • 🧽 Eraser Tool - Erase strokes with configurable eraser size
  • 🖼️ Image Support - Add, move, resize, and rotate images
  • 🔍 Pan & Zoom - Navigate large canvases with mouse wheel, touch gestures, or middle-click
  • 📐 Grid Background - Optional customizable grid overlay
  • 🧰 Floating Toolbox - Draggable toolbar with buttons, sliders, color pickers, and file inputs
  • 📺 Fullscreen Mode - Expand whiteboard to fullscreen
  • 💾 Export/Import JSON - Save and load whiteboard state
  • 🖼️ Export to Image - Download whiteboard as PNG/JPEG with custom resolution
  • 🔄 Auto-fit Preview - Automatically scale content to fit container
  • ↩️ Undo Support - Undo last stroke
  • Hand Mode - Pan canvas by dragging without holding special keys
  • 🖊️ Stylus Support - Full support for Samsung S-Pen, Apple Pencil, and other styluses
  • 👆 Touch Support - Multi-touch gestures for pinch-to-zoom and two-finger pan
  • 🎯 Pen Only Mode - Ignore finger touch, use only stylus input (iOS/Android)
  • 📏 Dimensions - Add measurement annotations with customizable values

Installation

npm install --save @savio99/react-draw
# or
yarn add @savio99/react-draw

Quick Start

Option 1: Use DrawingBoard (Complete Solution)

The easiest way to get started. DrawingBoard is a ready-to-use component with all features pre-configured:

import { DrawingBoard } from '@savio99/react-draw';

function App() {
  return (
    <div style={{ height: '100vh' }}>
      <DrawingBoard 
        showGrid={true}
        onChangeStrokes={(strokes) => console.log('Strokes changed:', strokes.length)}
      />
    </div>
  );
}

Option 2: Use Whiteboard (Custom Solution)

For full control, use the Whiteboard component directly:

import { useRef, useState } from 'react';
import Whiteboard, { Stroke } from '@savio99/react-draw';

function App() {
  const whiteboard = useRef<Whiteboard>(null);
  const [strokes, setStrokes] = useState<Stroke[]>([]);

  return (
    <Whiteboard
      ref={whiteboard}
      containerStyle={{
        style: {
          border: '2px solid black',
          borderRadius: 10,
          height: '80vh'
        }
      }}
      onChangeStrokes={(strokes) => setStrokes(strokes || [])}
    />
  );
}

DrawingBoard Component

A complete, ready-to-use drawing board with all features enabled. Includes a floating toolbox, mode switching, export/import, and more.

DrawingBoard Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | initialStrokes | Stroke[] | [] | Initial strokes to display | | initialImages | SketchImage[] | [] | Initial images to display | | initialDimensions | DimensionData[] | [] | Initial dimensions to display | | showGrid | boolean | true | Whether to show the grid | | gridSize | number | 25 | Grid size in pixels | | gridColor | string | '#e0e0e0' | Grid line color | | gridOpacity | number | 0.8 | Grid line opacity (0-1) | | minZoom | number | 0.25 | Minimum zoom level | | maxZoom | number | 4 | Maximum zoom level | | dimensionColor | string | '#ff5722' | Color for dimension lines | | defaultPenColor | string | '#000000' | Default pen color | | defaultPenWidth | number | 4 | Default pen width | | style | CSSProperties | - | Container style | | colorPalette | string[] | (10 colors) | Color palette for the picker | | toolboxPosition | {x, y} | {x:20, y:20} | Toolbox initial position | | toolboxOrientation | 'horizontal' \| 'vertical' | 'vertical' | Toolbox orientation | | additionalActions | ToolboxAction[] | [] | Custom actions to add | | hideActions | string[] | [] | Hide default actions by id | | labels | object | (Italian) | Labels for UI elements (i18n) | | onChangeStrokes | function | - | Callback when strokes change | | onChangeImages | function | - | Callback when images change | | onChangeDimensions | function | - | Callback when dimensions change | | onFullscreenChange | function | - | Callback when fullscreen changes |

DrawingBoard Ref Methods

const boardRef = useRef<DrawingBoardRef>(null);

// Export data
const data = boardRef.current?.exportData();
const json = boardRef.current?.exportJSON();

// Import data
boardRef.current?.importData(data);
boardRef.current?.importJSON(jsonString);

// Download as image
await boardRef.current?.downloadImage('my-drawing', { format: 'png', scale: 2 });

// Other actions
boardRef.current?.clear();
boardRef.current?.undo();
boardRef.current?.resetView();
boardRef.current?.toggleFullscreen();

// Access underlying Whiteboard
const whiteboard = boardRef.current?.getWhiteboard();

Customizing Labels (i18n)

<DrawingBoard
  labels={{
    pen: 'Pen',
    hand: 'Pan',
    dimension: 'Measure',
    select: 'Select',
    eraser: 'Eraser',
    penOnly: 'Stylus Only',
    color: 'Pen Color',
    strokeWidth: 'Pen Size',
    addImage: 'Add Image',
    undo: 'Undo',
    clear: 'Clear All',
    grid: 'Toggle Grid',
    fullscreen: 'Fullscreen',
    resetView: 'Reset View',
    modeLabel: 'Mode',
    penOnlyActive: 'Stylus Only active',
    instructions: 'Ctrl + scroll to zoom, middle-click or two fingers to pan'
  }}
/>

API Reference

Whiteboard Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | enabled | boolean | true | Enable/disable drawing. Set to false for mouse mode (select/move images) | | containerStyle | object | - | Container div props including style | | strokeColor | string | '#000000' | Initial stroke color | | strokeWidth | number | 4 | Initial stroke width | | strokes | Stroke[] | - | Controlled strokes array | | initialStrokes | Stroke[] | [] | Initial strokes (uncontrolled) | | onChangeStrokes | (strokes?: Stroke[]) => void | - | Callback when strokes change | | images | SketchImage[] | - | Controlled images array | | initialImages | SketchImage[] | [] | Initial images (uncontrolled) | | onChangeImages | (images: SketchImage[]) => void | - | Callback when images change | | showGrid | boolean | false | Show grid background | | gridSize | number | 20 | Grid cell size in pixels | | gridColor | string | '#cccccc' | Grid line color | | gridOpacity | number | 0.5 | Grid line opacity (0-1) | | enablePan | boolean | false | Enable panning (middle-click or two-finger touch) | | enableZoom | boolean | false | Enable zooming (Ctrl+scroll or pinch) | | minZoom | number | 0.5 | Minimum zoom level | | maxZoom | number | 3 | Maximum zoom level | | onFullscreenChange | (isFullscreen: boolean) => void | - | Callback when fullscreen state changes | | autoFit | boolean | false | Auto-fit content to container (useful for preview) | | autoFitPadding | number | 20 | Padding around content when auto-fitting | | eraserWidth | number | 20 | Eraser width in pixels | | children | ReactNode | - | Children (e.g., FloatingToolbox) | | mode | 'pen' \| 'hand' \| 'dimension' \| 'mouse' \| 'eraser' | 'pen' | Current interaction mode | | onModeChange | (mode: WhiteboardMode) => void | - | Callback when mode changes | | penOnly | boolean | false | Only accept stylus input, ignore finger touch (iOS/Android) | | onPenOnlyChange | (penOnly: boolean) => void | - | Callback when penOnly changes | | dimensions | DimensionData[] | - | Controlled dimensions array | | initialDimensions | DimensionData[] | [] | Initial dimensions (uncontrolled) | | onChangeDimensions | (dimensions: DimensionData[]) => void | - | Callback when dimensions change | | dimensionColor | string | '#ff5722' | Default color for new dimensions |

Whiteboard Methods

Access methods via ref:

const whiteboard = useRef<Whiteboard>(null);

// Then use:
whiteboard.current?.methodName();

Drawing Methods

| Method | Description | |--------|-------------| | undo() | Undo the last stroke | | clear() | Clear all strokes and images | | clearStrokes() | Clear only strokes | | clearImages() | Clear only images | | changeColor(color: string) | Change stroke color | | changeStrokeWidth(width: number) | Change stroke width |

Image Methods

| Method | Description | |--------|-------------| | addImage(src, x?, y?, width?, height?) | Add image to whiteboard | | removeImage(imageId: string) | Remove image by ID | | updateImage(imageId, updates) | Update image properties | | selectImage(imageId: string \| null) | Select/deselect image | | getImages() | Get all images |

Mode Methods

| Method | Description | |--------|-------------| | setMode(mode: WhiteboardMode) | Set interaction mode ('pen', 'hand', 'dimension', 'mouse', 'eraser') | | getMode() | Get current interaction mode | | setPenOnly(enabled: boolean) | Enable/disable stylus-only mode | | getPenOnly() | Check if penOnly mode is enabled |

Dimension Methods

| Method | Description | |--------|-------------| | addDimension(startX, startY, endX, endY, value?) | Add dimension line | | removeDimension(dimensionId: string) | Remove dimension by ID | | updateDimension(dimensionId, updates) | Update dimension properties | | selectDimension(dimensionId: string \| null) | Select/deselect dimension | | getDimensions() | Get all dimensions | | clearDimensions() | Clear all dimensions | | editDimensionValue(dimensionId: string) | Open modal to edit dimension value |

View Methods

| Method | Description | |--------|-------------| | setPan(x: number, y: number) | Set pan position | | setZoom(scale: number) | Set zoom level | | resetView() | Reset pan and zoom to defaults | | fitToContent(padding?: number) | Fit view to show all content | | toggleFullscreen() | Toggle fullscreen mode | | isFullscreen() | Check if in fullscreen mode | | getBounds() | Get bounding box of all content |

Export/Import Methods

| Method | Description | |--------|-------------| | exportData() | Export as WhiteboardData object | | exportJSON() | Export as JSON string | | importData(data, options?) | Import from WhiteboardData object | | importJSON(json, options?) | Import from JSON string | | exportToImage(options?) | Export as data URL | | downloadImage(filename?, options?) | Download as image file |

Types

Stroke

interface Stroke {
  box: { width: number; height: number };
  points: Point[];
  color: string;
  width: number;
}

SketchImage

interface SketchImage {
  id: string;
  src: string;
  x: number;
  y: number;
  width: number;
  height: number;
  rotation?: number;
  opacity?: number;
}

DimensionData

interface DimensionData {
  id: string;
  startX: number;
  startY: number;
  endX: number;
  endY: number;
  value: string;
  color?: string;
  fontSize?: number;
  lineWidth?: number;
}

WhiteboardMode

type WhiteboardMode = 'pen' | 'hand' | 'dimension' | 'mouse' | 'eraser';

WhiteboardData

interface WhiteboardData {
  version: string;
  strokes: Stroke[];
  images: SketchImage[];
  viewState?: {
    panX: number;
    panY: number;
    scale: number;
  };
}

Export Image Options

interface ExportImageOptions {
  format?: 'png' | 'jpeg';
  quality?: number;        // 0-1, for JPEG
  width?: number;          // Output width
  height?: number;         // Output height
  scale?: number;          // Scale factor (e.g., 2 for 2x resolution)
  backgroundColor?: string;
}

Examples

Basic Drawing with Controls

import { useRef, useState } from 'react';
import Whiteboard, { Stroke } from '@savio99/react-draw';

function DrawingApp() {
  const whiteboard = useRef<Whiteboard>(null);

  return (
    <div>
      <div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
        <button onClick={() => whiteboard.current?.undo()}>Undo</button>
        <button onClick={() => whiteboard.current?.clear()}>Clear</button>
        <input 
          type="color" 
          onChange={(e) => whiteboard.current?.changeColor(e.target.value)} 
        />
        <input 
          type="range" 
          min={1} 
          max={50}
          defaultValue={4}
          onChange={(e) => whiteboard.current?.changeStrokeWidth(parseInt(e.target.value))} 
        />
      </div>
      <Whiteboard
        ref={whiteboard}
        containerStyle={{ style: { height: '500px', border: '1px solid #ccc' } }}
      />
    </div>
  );
}

With Grid and Pan/Zoom

<Whiteboard
  ref={whiteboard}
  showGrid={true}
  gridSize={25}
  gridColor="#e0e0e0"
  gridOpacity={0.8}
  enablePan={true}
  enableZoom={true}
  minZoom={0.25}
  maxZoom={4}
  containerStyle={{ style: { height: '100vh' } }}
/>

With FloatingToolbox

import Whiteboard, { FloatingToolbox, ToolboxAction } from '@savio99/react-draw';

function App() {
  const whiteboard = useRef<Whiteboard>(null);
  const [color, setColor] = useState('#000000');
  const [strokeWidth, setStrokeWidth] = useState(4);

  const actions: ToolboxAction[] = [
    {
      id: 'color',
      label: 'Color',
      type: 'colorPicker',
      value: color,
      onChange: (value) => {
        setColor(value as string);
        whiteboard.current?.changeColor(value as string);
      },
      colors: ['#000000', '#ff0000', '#00ff00', '#0000ff', '#ffff00']
    },
    {
      id: 'size',
      label: 'Size',
      type: 'slider',
      value: strokeWidth,
      min: 1,
      max: 50,
      onChange: (value) => {
        setStrokeWidth(value as number);
        whiteboard.current?.changeStrokeWidth(value as number);
      }
    },
    {
      id: 'addImage',
      label: 'Add Image',
      type: 'file',
      accept: 'image/*',
      onChange: (src) => whiteboard.current?.addImage(src as string),
      icon: <span>🖼️</span>
    },
    {
      id: 'undo',
      label: 'Undo',
      onClick: () => whiteboard.current?.undo(),
      icon: <span>↩️</span>
    },
    {
      id: 'clear',
      label: 'Clear',
      onClick: () => whiteboard.current?.clear(),
      icon: <span>🗑️</span>
    }
  ];

  return (
    <Whiteboard ref={whiteboard} containerStyle={{ style: { height: '100vh' } }}>
      <FloatingToolbox
        actions={actions}
        initialPosition={{ x: 20, y: 20 }}
        orientation="vertical"
        containerRef={whiteboard.current?.getContainerRef()}
      />
    </Whiteboard>
  );
}

Mouse Mode for Image Selection

function App() {
  const whiteboard = useRef<Whiteboard>(null);
  const [mouseMode, setMouseMode] = useState(false);

  return (
    <div>
      <button onClick={() => setMouseMode(!mouseMode)}>
        {mouseMode ? 'Drawing Mode' : 'Mouse Mode'}
      </button>
      <Whiteboard
        ref={whiteboard}
        enabled={!mouseMode} // Disable drawing in mouse mode
        containerStyle={{ style: { height: '500px' } }}
      />
    </div>
  );
}

Mirror Preview with Auto-Fit

function App() {
  const [strokes, setStrokes] = useState<Stroke[]>([]);
  const [images, setImages] = useState<SketchImage[]>([]);

  return (
    <div>
      {/* Main whiteboard */}
      <Whiteboard
        onChangeStrokes={(s) => setStrokes(s || [])}
        onChangeImages={(i) => setImages(i)}
        containerStyle={{ style: { height: '60vh' } }}
      />
      
      {/* Mirror preview - auto-fits content */}
      <Whiteboard
        strokes={strokes}
        images={images}
        enabled={false}
        autoFit={true}
        autoFitPadding={30}
        containerStyle={{ 
          style: { 
            height: 200, 
            width: '50%', 
            margin: '0 auto',
            border: '1px solid #ccc'
          } 
        }}
      />
    </div>
  );
}

Export/Import JSON

function App() {
  const whiteboard = useRef<Whiteboard>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const handleExport = () => {
    const data = whiteboard.current?.exportData();
    if (!data) return;
    
    const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'whiteboard.json';
    a.click();
    URL.revokeObjectURL(url);
  };

  const handleImport = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;
    
    const reader = new FileReader();
    reader.onload = (event) => {
      const json = event.target?.result as string;
      whiteboard.current?.importJSON(json);
    };
    reader.readAsText(file);
  };

  return (
    <div>
      <button onClick={handleExport}>Export</button>
      <button onClick={() => fileInputRef.current?.click()}>Import</button>
      <input
        ref={fileInputRef}
        type="file"
        accept=".json"
        onChange={handleImport}
        style={{ display: 'none' }}
      />
      <Whiteboard ref={whiteboard} containerStyle={{ style: { height: '500px' } }} />
    </div>
  );
}

Export to Image

function App() {
  const whiteboard = useRef<Whiteboard>(null);

  const handleExportPNG = async () => {
    await whiteboard.current?.downloadImage('my-drawing.png', {
      format: 'png',
      scale: 2, // 2x resolution
      backgroundColor: '#ffffff'
    });
  };

  const handleExportJPEG = async () => {
    await whiteboard.current?.downloadImage('my-drawing.jpg', {
      format: 'jpeg',
      quality: 0.9,
      width: 1920, // Fixed width
      backgroundColor: '#ffffff'
    });
  };

  const handleGetDataUrl = async () => {
    const dataUrl = await whiteboard.current?.exportToImage({
      format: 'png',
      scale: 1
    });
    console.log(dataUrl); // Can be used as img src
  };

  return (
    <div>
      <button onClick={handleExportPNG}>Export PNG (2x)</button>
      <button onClick={handleExportJPEG}>Export JPEG (1920px)</button>
      <button onClick={handleGetDataUrl}>Get Data URL</button>
      <Whiteboard ref={whiteboard} containerStyle={{ style: { height: '500px' } }} />
    </div>
  );
}

Fullscreen Mode

function App() {
  const whiteboard = useRef<Whiteboard>(null);
  const [isFullscreen, setIsFullscreen] = useState(false);

  return (
    <div>
      <button onClick={() => whiteboard.current?.toggleFullscreen()}>
        {isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}
      </button>
      <Whiteboard
        ref={whiteboard}
        onFullscreenChange={setIsFullscreen}
        containerStyle={{ 
          style: { 
            height: '500px',
            backgroundColor: '#ffffff' // Background in fullscreen
          } 
        }}
      />
    </div>
  );
}

Mode Switching (Pen, Hand, Dimension, Mouse, Eraser)

import { useRef, useState } from 'react';
import Whiteboard, { WhiteboardMode, FloatingToolbox, ToolboxAction } from '@savio99/react-draw';

function App() {
  const whiteboard = useRef<Whiteboard>(null);
  const [mode, setMode] = useState<WhiteboardMode>('pen');
  const [penOnly, setPenOnly] = useState(false);

  const actions: ToolboxAction[] = [
    {
      id: 'pen',
      label: 'Pen',
      active: mode === 'pen',
      onClick: () => {
        setMode('pen');
        whiteboard.current?.setMode('pen');
      },
      icon: <span>✏️</span>
    },
    {
      id: 'hand',
      label: 'Hand (Pan)',
      active: mode === 'hand',
      onClick: () => {
        setMode('hand');
        whiteboard.current?.setMode('hand');
      },
      icon: <span>✋</span>
    },
    {
      id: 'dimension',
      label: 'Dimension',
      active: mode === 'dimension',
      onClick: () => {
        setMode('dimension');
        whiteboard.current?.setMode('dimension');
      },
      icon: <span>📏</span>
    },
    {
      id: 'mouse',
      label: 'Select',
      active: mode === 'mouse',
      onClick: () => {
        setMode('mouse');
        whiteboard.current?.setMode('mouse');
      },
      icon: <span>🖱️</span>
    },
    {
      id: 'eraser',
      label: 'Eraser',
      active: mode === 'eraser',
      onClick: () => {
        setMode('eraser');
        whiteboard.current?.setMode('eraser');
      },
      icon: <span>🧽</span>
    },
    {
      id: 'penOnly',
      label: 'Stylus Only',
      active: penOnly,
      onClick: () => {
        const newValue = !penOnly;
        setPenOnly(newValue);
        whiteboard.current?.setPenOnly(newValue);
      },
      icon: <span>🖊️</span>
    }
  ];

  return (
    <Whiteboard
      ref={whiteboard}
      mode={mode}
      penOnly={penOnly}
      enablePan={true}
      enableZoom={true}
      containerStyle={{ style: { height: '100vh' } }}
    >
      <FloatingToolbox
        actions={actions}
        initialPosition={{ x: 20, y: 20 }}
        orientation="vertical"
      />
    </Whiteboard>
  );
}

Working with Dimensions

import { useRef, useState } from 'react';
import Whiteboard, { DimensionData } from '@savio99/react-draw';

function App() {
  const whiteboard = useRef<Whiteboard>(null);
  const [dimensions, setDimensions] = useState<DimensionData[]>([]);

  return (
    <div>
      <div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
        <button onClick={() => whiteboard.current?.setMode('dimension')}>
          Add Dimension
        </button>
        <button onClick={() => whiteboard.current?.setMode('mouse')}>
          Select Mode
        </button>
        <button onClick={() => whiteboard.current?.clearDimensions()}>
          Clear Dimensions
        </button>
      </div>
      <Whiteboard
        ref={whiteboard}
        enablePan={true}
        enableZoom={true}
        onChangeDimensions={setDimensions}
        dimensionColor="#ff5722"
        containerStyle={{ style: { height: '500px', border: '1px solid #ccc' } }}
      />
      <div>
        <h4>Dimensions:</h4>
        <ul>
          {dimensions.map(d => (
            <li key={d.id}>{d.value || 'No value'}</li>
          ))}
        </ul>
      </div>
    </div>
  );
}

FloatingToolbox

A draggable toolbar component that can be placed inside the Whiteboard.

FloatingToolbox Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | actions | ToolboxAction[] | - | Array of toolbar actions | | visible | boolean | true | Show/hide toolbox | | initialPosition | { x: number; y: number } | { x: 20, y: 20 } | Initial position | | orientation | 'horizontal' \| 'vertical' | 'vertical' | Layout direction | | style | CSSProperties | - | Additional styles | | containerRef | RefObject<HTMLElement> | - | Reference to whiteboard container for bounds |

ToolboxAction Types

interface ToolboxAction {
  id: string;
  label: string;
  icon?: ReactNode;
  onClick?: () => void;
  active?: boolean;
  type?: 'button' | 'color' | 'number' | 'file' | 'slider' | 'colorPicker';
  value?: string | number | boolean;
  onChange?: (value: string | number | boolean) => void;
  min?: number;    // For slider
  max?: number;    // For slider
  accept?: string; // For file input
  colors?: string[]; // For colorPicker
}

Action Types

  • button (default): Simple click button
  • colorPicker: Color grid popup with presets
  • slider: Range slider with popup
  • file: File input (returns data URL for images)
  • color: Native color input
  • number: Native number input

Keyboard, Mouse & Touch Controls

| Action | Control | |--------|---------| | Zoom | Ctrl + Scroll, pinch gesture (two fingers), or mouse wheel | | Pan | Middle mouse button drag, two-finger touch, or Hand mode | | Draw | Left mouse button, stylus, or single-finger touch (in Pen mode) | | Erase | Left mouse button, stylus, or touch drag (in Eraser mode) | | Select Image | Click/tap image (in Mouse mode or when enabled={false}) | | Move Image | Drag selected image with mouse, stylus, or touch | | Resize Image | Drag corner handles with mouse, stylus, or touch | | Add Dimension | Click/tap and drag (in Dimension mode) | | Edit Dimension | Double-click/tap dimension (in Mouse mode) | | Select Dimension | Click/tap dimension (in Mouse mode) | | Move Dimension | Drag dimension body (in Mouse mode) | | Resize Dimension | Drag endpoint handles (in Mouse mode) |

Interaction Modes

| Mode | Description | |------|-------------| | pen | Default drawing mode. Left-click/touch/stylus draws strokes | | hand | Pan mode. Left-click/touch/stylus pans the canvas | | dimension | Dimension mode. Click/tap and drag to add measurement lines | | mouse | Selection mode. Click/tap to select images, dimensions, or strokes | | eraser | Eraser mode. Drag to erase strokes that intersect with the eraser |

Stylus & Touch Support

The library provides full support for stylus input (Samsung S-Pen, Apple Pencil, Surface Pen, etc.) and touch gestures:

  • Stylus Detection: Automatically detects pen vs touch vs mouse input
  • Pressure Sensitivity: Works with pressure-sensitive styluses
  • Palm Rejection: When penOnly is enabled, finger touches are ignored for drawing but can still be used for panning
  • Multi-touch Gestures: Two-finger pinch-to-zoom and pan work in all modes
  • Touch-friendly UI: All toolbox buttons and controls work with touch input

When penOnly is enabled, the whiteboard ignores regular finger touches for drawing and only responds to stylus/pen input. Finger touches will pan the canvas instead, providing a natural drawing experience on tablets.

License

MIT © savio-99