react-sketch-canvas

repository·main·Indexed 20 days ago

https://github.com/vinothpandian/react-sketch-canvas

A freehand vector drawing component for React that uses SVG as its canvas. It supports mouse, touch, and graphic tablet inputs on desktop and mobile. The library provides the ReactSketchCanvas component for embedding canvases, allowing customization of stroke color and width, background images, and the ability to export drawings as SVG markup or raster images (PNG, JPEG) via a ref-based API.

Tokens
21.1K
Snippets
74
Records
103
Agent score
68%

What's inside react-sketch-canvas

  1. Overview of React Sketch Canvas

    main

    React Sketch Canvas is a React component designed for freehand vector drawing using SVG. Unlike pixel-based <canvas> drawing, this library records every stroke as SVG path data.

    Key Benefits of the SVG approach:

    • Scalability: Drawings can be rendered at different sizes without blurry edges.
    • Persistence: You can save stroke paths and reload them later.
    • Exportability: Easily export drawings as clean SVG files.
    • Use Cases: Ideal for signature capture, image annotation, whiteboarding, and review screens where drawings need to be displayed without being editable.
  2. Use the low-level Canvas component for custom state management

    main

    While ReactSketchCanvas is the standard component that manages its own undo/redo history, eraser mode, and active strokes, you can use the Canvas component directly to take full control of the drawing state.

    Use Canvas when you need to implement:

    • Collaborative editors: Syncing paths via CRDTs or a command bus.
    • External state machines: Routing pointer events through a reducer or store.
    • Custom history stacks: Managing undo/redo logic outside the component.
    • Controlled path inspection: Rendering existing paths and reporting pointer points without creating new strokes (useful for annotation inspectors or multiplayer cursors).
    • Read-only previews: Displaying paths in a preview pane without enabling user input.
  3. Handle canvas layout in scrolling and transformed containers

    main

    The ReactSketchCanvas component is designed to prevent coordinate drift when placed inside complex layouts. It calculates pointer positions by reading viewport-relative coordinates and subtracting the canvas's own getBoundingClientRect().

    Because both the pointer coordinates and the canvas bounding rectangle update in sync with page scrolls, ancestor scrolls, or CSS transforms, strokes will land exactly where the pointer is regardless of the layout configuration. This makes the component compatible with:

    1. Scrollable parents: Containers with overflow: auto or overflow: scroll.
    2. Nested scroll containers: Multiple axes of scrolling (e.g., a horizontal scroll inside a vertical scroll).
    3. Scaled parents: Ancestors using CSS transform: scale().
  4. Use ReactSketchCanvasRef to control the canvas

    main

    The ReactSketchCanvasRef is an imperative API exposed via a ref on the ReactSketchCanvas component. It allows parent components to programmatically control drawing modes, manage history (undo/redo), handle exports, and load saved paths.

    // Example of how to access the ref
    const canvasRef = useRef<ReactSketchCanvasRef>(null);
    
    <ReactSketchCanvas ref={canvasRef} />
  5. Use CanvasProps for custom state management

    main

    The Canvas component is a low-level, controlled component. While most users should use ReactSketchCanvasProps via the ReactSketchCanvas component, you can use CanvasProps if you are building a custom state manager around the low-level SVG canvas. Because it is controlled, you must pass the complete paths list for every render to maintain the drawing state.

    // Example of the controlled pattern required by Canvas
    <Canvas 
      paths={myPathsState} 
      onPointerDown={(point) => handleDown(point)} 
      // ... other props
    />
  6. How stroke coloring works: Per-stroke vs. Bulk recoloring

    main

    The behavior of color updates depends on how you pass the color value to the strokeColor prop:

    Per-stroke colors

    By default, strokeColor is captured at the moment a stroke is drawn. If you change the strokeColor prop in your component state between drawing actions, each stroke will retain the color it had when it was originally created. This allows for multi-colored sketches.

    Bulk recoloring

    To recolor all existing and future strokes simultaneously (e.g., for theme switching), you must use a CSS Variable (e.g., strokeColor="var(--stroke-color)") or currentColor. When the value of the CSS variable changes in your stylesheet, all strokes referencing that variable will update immediately.

    // For bulk recoloring, use a CSS variable
    <ReactSketchCanvas strokeColor="var(--stroke-color)" />
  7. Switch between drawing and erasing modes

    main

    To switch between adding ink and removing it, use the eraseMode(boolean) method on the ReactSketchCanvasRef.

    • Call eraseMode(true) to enable erasing.
    • Call eraseMode(false) to return to drawing mode.

    It is recommended to manage the current mode in your component's state so your UI (like toolbars) can reflect the active tool.

    import React, { useRef, useState } from "react";
    import {
      ReactSketchCanvas,
      type ReactSketchCanvasRef,
    } from "react-sketch-canvas";
    
    export function DrawingToolbar() {
      const canvasRef = useRef<ReactSketchCanvasRef>(null);
      const [mode, setMode] = useState<"draw" | "erase">("draw");
    
      return (
        <>
          <ReactSketchCanvas ref={canvasRef} />
          <button
            type="button"
            onClick={() => {
              canvasRef.current?.eraseMode(false);
              setMode("draw");
            }}
          >
            Draw
          </button>
          <button
            type="button"
            onClick={() => {
              canvasRef.current?.eraseMode(true);
              setMode("erase");
            }}
          >
            Erase
          </button>
        </>
      );
    }
  8. Measure active sketching time

    main

    You can measure the total time a user spends actively drawing (excluding idle time between strokes) by enabling timestamps on the canvas.

    1. Pass the withTimestamp prop to the ReactSketchCanvas component.
    2. Use the getSketchingTime() method on the canvas ref to retrieve the total active time in milliseconds.

    Important: The withTimestamp prop must be set before any strokes are drawn. If you enable it after drawing, existing strokes will not have timestamps, and getSketchingTime() will return 0 for those strokes.

    import React, { useRef, useState } from "react";
    import {
      ReactSketchCanvas,
      type ReactSketchCanvasRef,
    } from "react-sketch-canvas";
    
    export function TimedCanvas() {
      const canvasRef = useRef<ReactSketchCanvasRef>(null);
      const [ms, setMs] = useState(0);
    
      return (
        <>
          {/* 1. Enable timestamps via the withTimestamp prop */}
          <ReactSketchCanvas ref={canvasRef} withTimestamp />
          
          <button
            type="button"
            onClick={async () => {
              /* 2. Call getSketchingTime() on the ref */}
              const total = (await canvasRef.current?.getSketchingTime()) ?? 0;
              setMs(total);
            }}
          >
            Read sketching time
          </button>
          <p>{ms} ms</p>
        </>
      );
    }
  9. Optimize performance for react-sketch-canvas

    main

    To maintain high performance, especially with dense SVG drawings or heavy path rendering, follow these best practices:

    • Prevent unnecessary re-renders: Do not allow the canvas to re-render on every stroke. If you have a toolbar, keep its state outside the component subtree containing the canvas, or use React.memo to ensure toolbar buttons do not re-render when a new stroke is added.
    • Batch path loading: When initializing or updating the canvas with multiple paths, use a single call to loadPaths() instead of calling it repeatedly for each individual stroke. Batching is significantly more efficient.
    • Disable timestamps if unnecessary: If your application does not require timing data for strokes, avoid using withTimestamp. Enabling it performs two Date.now() calls per stroke and increases the size of the saved data.
    • Throttle high-frequency input: While standard pointer events are handled efficiently, if you are using synthetic input streams, you should cap or throttle the number of points per stroke to prevent excessive data accumulation.