useCanvas

The engine behind the canvas component. It tracks a camera (x, y, scale) and wires up pan, wheel + pinch zoom, spacebar grab, marquee selection, keyboard zoom shortcuts and fit-to-content, all via requestAnimationFrame, with no dependencies. Most of the time you'll use <Canvas> rather than the hook directly.

Pan · zoom · select

Scroll or two-finger drag to pan. Pinch or ⌘/Ctrl + scroll to zoom. Drag on empty space to marquee-select cards.

Another card
And another

Installation

npx shadcn@latest add https://canvas.blode.co/r/use-canvas.json

Usage

const { containerRef, contentRef, cursorMode, handlers, initialStyle } =
  useCanvas({ initial: { scale: 0.85, x: 0, y: 0 } });
 
return (
  <div className="fixed inset-0" ref={containerRef} {...handlers}>
    <div className="origin-top-left" ref={contentRef} style={initialStyle}>
      {children}
    </div>
  </div>
);

Options

PropTypeDefaultDescription
initialPartial<{ x; y; scale }>Starting camera. Scale defaults to 0.85, x/y to 0.
onCameraSettle(state) => voidFires after pan/zoom settles, with the final camera.
onSelectionChange(rect | null) => voidFires while a marquee is being dragged.
onSelectionEnd(rect | null) => voidFires when the marquee gesture ends.

Returns

PropTypeDefaultDescription
containerRefRef<HTMLDivElement>Attach to the fixed viewport element.
contentRefRef<HTMLDivElement>Attach to the transformed content layer.
handlersPointerEventHandlersSpread onto the container to enable pan/zoom/select.
initialStyleCSSPropertiesInitial transform for the content layer.
cursorMode"default" | "grab" | "grabbing" | "crosshair"Current cursor state for the viewport.
animateTo(target, duration?) => voidAnimate the camera to a target state.
fitToContent() => voidFrame all `[data-card-id]` children in the viewport.