Library / SDK
timmywil/panzoom avatar
timmywil/panzoom

Panzoom: CSS-transform panning and zooming for any element

A library for panning and zooming elements using CSS transforms :mag:

2,469 stars413 forksTypeScriptMIT

At a glance

What is it?
Panzoom is a small TypeScript library that adds pan and zoom behaviour to images, SVGs, canvases and iframes by writing CSS transforms. It is easy to adopt, but the async behaviour of its contain option and its event handling are the two things to understand before you commit.
Who is it for?
Adopt Panzoom if you need drag-to-pan and pinch-to-zoom on an image, SVG, canvas or iframe and you are happy to bind the zoom triggers yourself. Do not adopt it if you need a ready-made viewer with a toolbar, minimap or keyboard navigation, because the README documents none of those; you would be writing them around the library.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 29 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem Panzoom solves, and the elements it is meant for

Most pan-and-zoom implementations move an element by changing its position, or by rewriting its width and height. Both approaches tie the library to a specific kind of content: an image with known intrinsic dimensions, or a container whose layout you control. Panzoom takes a different route. The README states it uses CSS transforms rather than absolute positioning or width and height, which means the element being panned can be anything: an image, a video, an iframe, a canvas, or plain text. That is the core design decision, and it explains most of the library's behaviour.

The package is published as @panzoom/panzoom and the README puts it at roughly 3.7kb gzipped. It is written in TypeScript, ships type definitions, and targets browsers through pointer events, with pinch gesture support for zooming on iOS, Android and Windows Mobile. If your interface already contains a rendered thing that users want to inspect closely, Panzoom is the layer that makes that thing draggable and scalable without you restructuring the DOM around it.

How Panzoom applies transforms and binds input

Calling Panzoom(elem, options) returns an object with methods such as pan(), zoom(), zoomIn() and zoomWithWheel. The library writes a CSS transform onto the element and manages the transform-origin so that focal-point zooming calculates correctly. The README's FAQ notes that HTML elements default to '50% 50%' for transform-origin while SVG elements default to '0 0', and that Panzoom sets these to what it expects.

Input binding is split. Panning and pinch zooming are bound automatically when you construct the instance, unless disablePan is set. Zooming through a button or the mouse wheel is not bound for you; the README's usage example attaches zoomIn to a click and zoomWithWheel to a wheel event on the parent element. That split is deliberate and it is the first thing to internalise: the library handles the gesture layer, and you decide which controls drive zoom.

Two options change the event model. The canvas option treats the Panzoom element's parent as a canvas, so the down handler is bound to the parent rather than to the element, and the cursor style moves to the parent as well. The exclude option, with its default class panzoom-exclude, lets clickable children inside the panned element keep working, which the README presents as the fix for links that stop responding.

Installing Panzoom and wiring a first working view

The package installs from npm under the scoped name @panzoom/panzoom. The README also gives a yarn equivalent and a CDN script tag pointing at unpkg for version 4.6.2.

bash
npm install --save @panzoom/panzoom

After installing, import the default export. The README shows ES6, CommonJS, AMD and script-tag forms; the ES module form is the one most bundlers will use.

js
import Panzoom from '@panzoom/panzoom'

const elem = document.getElementById('panzoom-element')
const panzoom = Panzoom(elem, {
  maxScale: 5
})
panzoom.pan(10, 10)
panzoom.zoom(2, { animate: true })

Panning and pinch zooming are bound by the constructor, so the element responds to drag immediately. Zooming by wheel is a separate binding, and the README attaches it to the parent element rather than the element itself.

js
elem.parentElement.addEventListener('wheel', panzoom.zoomWithWheel)

With those two blocks in place you should see the element follow the pointer when dragged, and scale when the wheel turns over the parent. If you load the library from a script tag instead of a bundler, the README's CDN example uses https://unpkg.com/@panzoom/[email protected]/dist/panzoom.min.js. If the console reports that panzoom is not defined, the script tag is the first thing to check, because the UMD build exposes the global only after it loads.

The contain option and Panzoom's asynchronous ordering

The README devotes a section to what it calls the async nature of Panzoom, and it is the most useful thing in the documentation for anyone integrating the library. Setting one value and then another synchronously usually works. The README's example calls zoom(2) and then pan(100, 100) and says this is fine in most cases. But it warns that things start breaking when the contain option is set, because Panzoom needs the scale to be painted before it can retrieve proper dimensions.

The prescribed workaround is to defer the second call.

js
panzoom.zoom(2)
setTimeout(() => panzoom.pan(100, 100))

This is a real constraint, not a footnote. Any code path that sequences a zoom and a pan in the same tick, and that also relies on containment to keep the element inside its bounds, can compute against stale dimensions. The setTimeout in the README is a demonstration of the ordering problem, not a general-purpose fix; a production integration would more likely hook into whatever paint or frame callback the surrounding application already uses. Panzoom does not provide a promise or an event that tells you when the dimensions are ready, and the README does not document one.

Where Panzoom is the wrong choice

Panzoom is a gesture and transform library, not a viewer. The README documents options for animation, duration, easing, scale limits and event exclusion, and methods for pan and zoom. It does not document a toolbar, a minimap, keyboard navigation, double-tap-to-zoom presets, or a built-in reset control. If your product needs those, you are building them on top of the returned object, and the library's contribution shrinks to the transform math and pointer handling.

Event interception is the other sharp edge. An object tag can swallow events before they reach Panzoom, and the README's answer is to set pointer-events: none on the object and call Panzoom on a wrapper. Links inside a panned element stop working unless you mark them with the panzoom-exclude class, add them to the exclude option, or call stopImmediatePropagation() in a handler. These are all documented, but each one is a manual step that a heavier viewer library would handle for you.

There is also a rendering caveat for SVG text. The README links to an issue about text resizing oddly and recommends adding text-rendering="geometricPrecision" to the text elements. And in IE11, the README states that CSS animations and transitions do not work on SVG elements for the transform style, suggesting a tweening library with the setTransform option as a manual alternative.

How Panzoom compares with a full viewer such as OpenSeadragon

The closest alternative in spirit is a tiled image viewer such as OpenSeadragon. The difference is in what each one owns. OpenSeadragon expects an image source it can tile and fetch at multiple resolutions, and it brings a viewer chrome around that: navigation controls, a coordinate system, and a rendering pipeline tuned for very large images. Panzoom expects a DOM element that already exists and is already rendered, and it writes a CSS transform to it. There is no tiling, no tile server, no resolution pyramid.

That makes the choice mostly about content size and surrounding UI. A 4000-pixel diagram, a floor plan, an SVG map or an embedded canvas is a natural fit for Panzoom, because the browser can hold the whole thing and the GPU handles the transform. A gigapixel scan or a scanned map collection is not, because the browser cannot hold the whole thing at full resolution, and Panzoom has no mechanism for fetching pieces of it. The README's own FAQ points a canvas-based PDF viewer at a Stack Overflow answer about avoiding blur when scaled, which is a hint that high-resolution content needs handling outside the library.

Maintenance, licence and the cost of upgrading

The repository is not archived, and the last push was on 2026-09-02. The most recent release in the release list is 4.6.2, published on 2026-04-02, following 4.6.1 on 2025-12-02 and 4.6.0 on 2025-01-14. That is a slow but non-zero release cadence, and it is worth reading the release notes for 4.6.0 through 4.6.2 before upgrading rather than assuming a drop-in replacement.

Panzoom is MIT licensed, and the published package includes the licence file alongside src, dist and README. For most applications that means attribution requirements are minimal and there is no copyleft obligation on your own code, but the exact obligations depend on how you redistribute the library, and that is a question for your own counsel rather than something to settle from a README.

The upgrade surface is small. The package ships ES module, UMD and minified builds, and the README's CDN example pins an exact version, which is the safer pattern for a script tag: an unpinned URL can move under you. The one thing to re-verify after any upgrade is the async ordering described above, since it depends on when transforms are painted, and that is exactly the kind of behaviour a minor release can shift.

Editorial conclusion

Adopt Panzoom if you need drag-to-pan and pinch-to-zoom on an image, SVG, canvas or iframe and you are happy to bind the zoom triggers yourself. Do not adopt it if you need a ready-made viewer with a toolbar, minimap or keyboard navigation, because the README documents none of those; you would be writing them around the library. Before you commit, verify two things in a real browser: that your target element receives pointer events (the README's object-tag FAQ is the common failure), and that any sequence of zoom() and pan() calls behaves correctly with contain enabled, since the README states the scale must be painted before dimensions are read.

Frequently asked questions

How do I use Panzoom on an element?

Call Panzoom(elem, options) on the element you want to control. Panning and pinch zooming bind automatically, and you bind wheel or button zoom yourself, for example with elem.parentElement.addEventListener('wheel', panzoom.zoomWithWheel).

Why do I get "panzoom is not defined"?

The UMD build exposes the global only after the script loads, so check the order and the URL of your script tag. The README's CDN example loads https://unpkg.com/@panzoom/[email protected]/dist/panzoom.min.js.

What are the alternatives to Panzoom?

A tiled viewer such as OpenSeadragon is the main alternative in approach: it fetches image tiles at multiple resolutions and ships viewer controls, while Panzoom writes a CSS transform onto an element that is already rendered in the DOM.

What is pan zoom?

In this library it means panning and zooming an element: Panzoom writes a CSS transform to the element so it can be dragged and scaled, and the README states it uses transforms rather than absolute positioning or width and height.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. timmywil/panzoom on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/timmywil-panzoom.svg)](https://hysenlabs.com/projects/timmywil-panzoom)