◇

ZoomPan

3 parts

Demo

Loading demo…

Anatomy

Import the parts and compose them. Each part forwards attributes to its underlying element.

vue
import {
  ViewportRoot,
  ViewportSurface,
  ViewportContent,
} from '@robonen/primitives/canvas/zoom-pan';

<ViewportRoot>
  <ViewportSurface />
  <ViewportContent />
</ViewportRoot>

API Reference

ViewportRoot

ViewportRoot
PropTypeDescription
viewport?Viewport | undefinedTwo-way bound value (v-model:viewport).
Emits
EventPayloadDescription
update:viewport[value: Viewport | undefined]Emitted when v-model:viewport updates.
Exposes (template ref)
NameTypeDescription
getViewport() => ViewportCurrent viewport {x,y,zoom}.
setViewport(viewport: Viewport) => voidReplace the viewport (clamped).
zoomIn(factor?: number) => voidZoom in, anchored at the surface centre.
zoomOut(factor?: number) => voidZoom out, anchored at the surface centre.
zoomTo(zoom: number) => voidZoom to an absolute level, anchored at the surface centre.
zoomToPoint(zoom: number, point: XYPosition) => voidZoom to an absolute level, anchored at a surface-relative pixel point.
fit(bounds?: Rect | null, params?: FitParams) => voidFit bounds (content space) into view, centred with padding. Defaults to the root contentExtent; an explicit no-op when both are null. Respects the surface width === 0 || height === 0 early-return.
center(point?: XYPosition) => voidCentre a content-space point in the surface, keeping the current zoom.
panBy(dx: number, dy: number) => voidPan by a screen-pixel delta.
reset() => voidReset to { x: 0, y: 0, zoom: 1 } (clamped).
screenToContent(point: XYPosition) => XYPositionConvert a screen (client) point to content space.
contentToScreen(point: XYPosition) => XYPositionConvert a content-space point to a screen (client) point.
viewportWritableComputedRef<Viewport, Viewport>—

ViewportSurface

ViewportSurface

The clipping / interaction pane. Clips the content (`overflow: hidden`), disables native touch gestures (`touch-action: none`), reports its live bounding rect into the context as the screen origin for coordinate math, and hosts the wheel / pinch / drag / dblclick / keyboard pan-zoom layer (`useZoomPan`). Carries `role` / `tabindex` for the a11y keyboard layer and reflects gesture state via `data-panning` / `data-zooming` / `data-measured`. Rendered by `ViewportRoot` by default; place directly only when composing the parts manually.

No props or events — renders its element and forwards attributes.

ViewportContent

ViewportContent

The single transformed layer. Everything a consumer renders inside the viewport lives here, so pan/zoom is one GPU-composited `transform` rather than a per-element restyle. `transform-origin: 0 0` is required — the coordinate formulas in `utils.ts` assume top-left scaling. `will-change: transform` is toggled on ONLY while interacting (never permanently): a pinned hint locks the compositor's raster scale, so the cached texture is GPU-upscaled and the content blurs at high zoom — toggling it lets the layer re-rasterise crisply once motion settles (see `useInteractionState`). Pure presentational; renders the default slot.

No props or events — renders its element and forwards attributes.