component

Pan Zoom

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.

Overview

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---
import PanZoom from "../PanZoom.astro";
import Content from "./_content.astro";
---

<PanZoom size="md">
  <Content />
</PanZoom>

Large content

Set maxZoom to bound how far the user can zoom in. The view always opens fitted to the content with padding.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---
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>

SVG content

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>

Fit modes

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
100%
---
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>

Minimap

showMinimap adds an overview in the corner with a draggable rectangle marking the visible region — drag it to pan large content quickly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
100%
---
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>

Controlled zoom

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
150%
---
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>

Keyboard & accessibility

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
100%
---
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>

Custom styling

Every slot is restylable through passThrough, and behavior is tunable via wheelZoomSpeed, pinchSensitivity, constrainToBounds, and more.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---
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>
props · 28 shown · 28 total
animationDuration
number
default 150

Duration of smooth-zoom transitions, in milliseconds.

children slot svelte
Snippet

Content to pan and zoom — any DOM, image, or SVG.

constrainToBounds
boolean
default false

Constrain panning so the content edges cannot be dragged inside the viewport once the content covers it.

disabled
boolean
default false

Disable all interaction and dim the component.

doubleTapToZoom
boolean
default true

Enable double-tap-to-zoom on touch devices.

element binding svelte
HTMLDivElementnull

Bindable reference to the root container element.

feedback
"primary""neutral""success""warning""danger""inherit"

Semantic feedback color forwarded via `colorPalette`. Tints the focus ring, minimap rectangle, and accent surfaces.

fitMode
"contain""cover""actual-size"
default "contain"

How `reset()` and the reset button lay content out: fit-with-padding (`contain`), fill the viewport (`cover`), or 1:1 (`actual-size`).

label
string
default "Pan and zoom viewport"

Accessible label for the interactive viewport region.

maxZoom
number
default 5

Highest zoom level the user can reach.

minZoom
number

Lowest zoom level the user can reach. When omitted it is auto-computed to fit the content with padding (never below `0.01`).

onFullscreenChange svelte
(isFullscreen: boolean) => void

Fires when fullscreen is entered or exited.

onPanChange svelte
(translate: Point) => void

Fires whenever the pan translation changes.

onZoomChange svelte
(scale: number) => void

Fires whenever the zoom level changes (gesture, button, key, or set).

padding
number
default 16

Padding (px) kept around the content when fitting it to the viewport.

passThrough
PanZoomPassThrough< ContainerEl, ViewportEl, ContentEl, ControlsEl, ZoomInButtonEl, ZoomOutButtonEl, ResetButtonEl, FullscreenButtonEl, ZoomIndicatorEl, MinimapEl >

Per-slot style and HTML-attribute override bag.

pinchSensitivity
number
default 1

Pinch-gesture sensitivity. Lower values make pinch-zoom more sensitive.

resetOnDoubleClick
boolean
default true

Double-click (or double-tap) resets the view to the fitted state.

showControls
boolean
default true

Render the zoom in/out/reset (and optionally fullscreen) control buttons.

showFullscreenButton
boolean
default true

Render the fullscreen toggle button (within the controls).

showMinimap
boolean
default false

Render a minimap overview with a draggable viewport rectangle.

showZoomIndicator
boolean
default false

Render the live zoom-percentage readout.

size
"sm""md""lg"
default "md"

Visual size variant — sets the minimum height of the container.

smoothZoom
boolean
default true

Animate discrete zoom steps (buttons, keys, double-click). Drag and pinch are always instant. Ignored under `prefers-reduced-motion: reduce`.

wheelZoomSpeed
number
default 0.05

Wheel / trackpad zoom sensitivity. Higher zooms faster per wheel tick.

zoom
number

Initial / controlled zoom level. Two-way bindable in Svelte (`bind:zoom`) and controllable in React (`zoom` + `onZoomChange`).

zoomOnDoubleClick
boolean
default false

Double-click (or double-tap) zooms in toward the pointer instead of resetting. Takes precedence over `resetOnDoubleClick`.

zoomSpeed
number
default 0.2

Zoom step applied by the zoom-in/zoom-out buttons, `+`/`-` keys, and double-click-to-zoom.

Plus all standard <div> HTML attributes.

Type

  • Components
  • Blocks