component
A pan and zoom viewport for images, SVG, and any DOM content — shared across Astro, Svelte, and React from one framework-agnostic core. It supports mouse-drag and touch panning, pinch and wheel zooming, keyboard navigation, a live zoom readout, fit modes, fullscreen, and an optional minimap.
Drag to pan, Ctrl/⌘ + scroll (or pinch) to zoom, and double-click to reset. Focus the viewport for full keyboard control: arrow keys pan, +/- zoom, and 0 resets. The built-in controls zoom in/out, reset, and toggle fullscreen.
---
import PanZoom from "../PanZoom.astro";
import Content from "./_content.astro";
---
<PanZoom size="md">
<Content />
</PanZoom>Set maxZoom to bound how far the user can zoom in. The view always opens fitted to the content with padding.
---
import PanZoom from "../PanZoom.astro";
import Content from "./_content.astro";
---
<!-- A larger surface that benefits from a max zoom of 3. -->
<PanZoom size="lg" maxZoom={3}>
<Content />
</PanZoom>Any DOM works as content, including inline SVG. Use minZoom/maxZoom to frame the useful zoom range.
---
import PanZoom from "../PanZoom.astro";
---
<PanZoom size="md" minZoom={0.5} maxZoom={4}>
<svg
width="480"
height="320"
viewBox="0 0 480 320"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="480" height="320" fill="#1e293b"></rect>
<circle cx="160" cy="160" r="90" fill="#38bdf8"></circle>
<rect x="260" y="80" width="140" height="140" rx="16" fill="#f472b6"></rect>
<path
d="M40 280 L240 40 L440 280 Z"
fill="none"
stroke="#fde047"
stroke-width="4"></path>
</svg>
</PanZoom>fitMode controls how the view lays content out on open and reset: contain (default, fit with padding), cover (fill the viewport), or actual-size (1:1). Enable showZoomIndicator for a live zoom-percentage readout.
---
import PanZoom from "../PanZoom.astro";
import Content from "./_content.astro";
---
<!-- `cover` fills the viewport instead of fitting with padding. -->
<PanZoom size="md" fitMode="cover" showZoomIndicator>
<Content />
</PanZoom>showMinimap adds an overview in the corner with a draggable rectangle marking the visible region — drag it to pan large content quickly.
---
import PanZoom from "../PanZoom.astro";
import Content from "./_content.astro";
---
<!-- The minimap shows the visible slice and is draggable to pan. -->
<PanZoom size="lg" showMinimap showZoomIndicator>
<Content />
</PanZoom>The zoom level is controllable and observable. In Svelte use bind:zoom; in React pass zoom + onZoomChange; in Astro seed the initial level with the zoom prop. onPanChange and onFullscreenChange are also available.
---
import PanZoom from "../PanZoom.astro";
import Content from "./_content.astro";
---
<!-- Seeded at 1.5x with a live zoom-percentage readout. -->
<PanZoom size="md" zoom={1.5} showZoomIndicator>
<Content />
</PanZoom>The viewport is a focusable region with an accessible label. Once focused, arrow keys pan, +/- zoom, and 0 resets. Smooth zoom transitions are skipped automatically under prefers-reduced-motion.
---
import PanZoom from "../PanZoom.astro";
import Content from "./_content.astro";
---
<!-- Focus the viewport, then use arrows to pan, +/- to zoom, 0 to reset. -->
<PanZoom size="md" showZoomIndicator label="Keyboard-navigable diagram">
<Content />
</PanZoom>Every slot is restylable through passThrough, and behavior is tunable via wheelZoomSpeed, pinchSensitivity, constrainToBounds, and more.
---
import PanZoom from "../PanZoom.astro";
import Content from "./_content.astro";
---
<!-- passThrough restyles slots; wheel zoom is faster and bounds are constrained. -->
<PanZoom
size="md"
wheelZoomSpeed={0.15}
maxZoom={8}
constrainToBounds
passThrough={{
container: {
style: { borderColor: "primary.border.accent", borderRadius: "lg" },
},
controls: { style: { bottom: "lg", right: "lg" } },
}}
>
<Content />
</PanZoom>150Duration of smooth-zoom transitions, in milliseconds.
Content to pan and zoom — any DOM, image, or SVG.
falseConstrain panning so the content edges cannot be dragged inside the viewport once the content covers it.
falseDisable all interaction and dim the component.
trueEnable double-tap-to-zoom on touch devices.
Bindable reference to the root container element.
Semantic feedback color forwarded via `colorPalette`. Tints the focus ring, minimap rectangle, and accent surfaces.
"contain"How `reset()` and the reset button lay content out: fit-with-padding (`contain`), fill the viewport (`cover`), or 1:1 (`actual-size`).
"Pan and zoom viewport"Accessible label for the interactive viewport region.
5Highest zoom level the user can reach.
Lowest zoom level the user can reach. When omitted it is auto-computed to fit the content with padding (never below `0.01`).
Fires when fullscreen is entered or exited.
Fires whenever the pan translation changes.
Fires whenever the zoom level changes (gesture, button, key, or set).
16Padding (px) kept around the content when fitting it to the viewport.
Per-slot style and HTML-attribute override bag.
1Pinch-gesture sensitivity. Lower values make pinch-zoom more sensitive.
trueDouble-click (or double-tap) resets the view to the fitted state.
trueRender the zoom in/out/reset (and optionally fullscreen) control buttons.
trueRender the fullscreen toggle button (within the controls).
falseRender a minimap overview with a draggable viewport rectangle.
falseRender the live zoom-percentage readout.
"md"Visual size variant — sets the minimum height of the container.
trueAnimate discrete zoom steps (buttons, keys, double-click). Drag and pinch are always instant. Ignored under `prefers-reduced-motion: reduce`.
0.05Wheel / trackpad zoom sensitivity. Higher zooms faster per wheel tick.
Initial / controlled zoom level. Two-way bindable in Svelte (`bind:zoom`) and controllable in React (`zoom` + `onZoomChange`).
falseDouble-click (or double-tap) zooms in toward the pointer instead of resetting. Takes precedence over `resetOnDoubleClick`.
0.2Zoom step applied by the zoom-in/zoom-out buttons, `+`/`-` keys, and double-click-to-zoom.
Plus all standard <div> HTML
attributes.