panzoom
Extensible, mobile-friendly pan and zoom library for DOM and SVG elements with kinetic scrolling.
Repository Health
Technical Analysis
Panzoom is a small, framework-free JavaScript library that adds drag-to-pan and pinch/wheel-to-zoom behavior to any DOM subtree or SVG element. It ships a pluggable controller architecture that transparently handles the differences between CSS transforms on regular DOM elements and matrix transforms on SVG, so the same API works for either without extra configuration.
Beyond basic panning and zooming, it supports kinetic (physics-based momentum) scrolling, configurable bounds so content can’t be dragged out of view, min/max zoom limits, a customizable transform origin, keyboard navigation, and an event system (pan/zoom/panstart/panend/transform) for reacting to user interaction. It can be installed via npm for bundler-based projects or dropped in via a script tag with a declarative pz- attribute API for zero-JavaScript setup.
What You Get
- A single
panzoom(element, options)call that adds full pan/zoom interaction to any DOM or SVG element with zero required configuration - Kinetic (momentum-based) scrolling after a drag, with tunable amplitude and time-constant physics
- Bounds and min/max zoom constraints so users can’t pan or zoom content out of a usable range
- An event API (
on/off/fire) forpan,panstart,panend,zoom,zoomend, andtransformevents - A declarative script-tag attachment mode (
query+pz-prefixed attributes) for use without a bundler - Hand-maintained TypeScript declarations (
index.d.ts) for full autocomplete in TS projects
Common Use Cases
- Adding pan/zoom to an interactive SVG diagram, map, or graph visualization embedded in a web page
- Building an image or canvas viewer where users can drag to pan and scroll/pinch to zoom
- Powering infinite-canvas or whiteboard-style UIs where a scene graph needs to be navigable
- Adding touch-friendly pan/zoom to a mobile web view without pulling in a full charting or mapping framework
Under The Hood
Architecture
The entry point (index.js, ~1,100 lines) implements createPanZoom, which detects the target element’s type via canAttach() checks and delegates DOM-specific vs. SVG-specific transform handling to two small, swappable controller modules (lib/makeDomController.js, lib/makeSvgController.js), each exposing the same narrow interface (getOwner, getBBox, getScreenCTM, applyTransform, initTransform) — a lightweight strategy pattern that keeps the core event/gesture logic identical regardless of target type. Core transform state lives in a small Transform class (lib/transform.js); momentum scrolling is isolated in lib/kinetic.js as an independently start/stop-able physics ticker driven by requestAnimationFrame; and text-selection suppression during drags is injected via lib/makeTextSelectionInterceptor.js. This keeps the module boundaries clean, but mouse, wheel, touch, pinch, and keyboard handling are all centralized as closures inside the single large entry file, making it the piece most exposed to change if the controller interface itself needs to evolve.
Tech Stack
Panzoom is dependency-light vanilla JavaScript (CommonJS, ES5-style) with no runtime framework: it pulls in only three small libraries by the same author — wheel (cross-browser wheel-event normalization), amator (tweening for smooth zoom animations), and ngraph.events (the pub/sub backing .on()/.off()/.fire()). Distribution builds run through Browserify into a UMD-style dist/panzoom.js, minified with uglify-js, for direct <script>/CDN use; tests run under tap with jsdom simulating a DOM so no headless browser is required. TypeScript consumers get hand-written ambient types in index.d.ts rather than types generated from source.
Code Quality
The test suite (test/panzoom.js, test/kinetic.js) exercises creation, min/max zoom, wheel-driven transform updates, pause/resume, and event firing through tap against a jsdom-simulated DOM, but the configured coverage thresholds are low (30% for branches/lines/statements/functions) and the SVG/DOM controller modules have no dedicated tests of their own. Error handling favors explicit throw new Error(...) guards for invalid usage (e.g. attaching to the root <svg> element) over a broader typed-error pattern. Naming is consistent camelCase throughout, GitHub Actions runs the test suite on push, and ESLint provides basic linting, though there’s no enforced formatter and the runtime JS and its hand-maintained .d.ts types can drift independently.
API Design
The public surface is a single default export, createPanZoom(domElement, options), returning an instance with a small, well-named API (moveTo, zoomTo, smoothZoom, getTransform, on/off/fire, pause/resume). Getting started needs exactly one line — panzoom(element) — with every option (bounds, zoomSpeed, minZoom/maxZoom, transformOrigin, filterKey) opt-in and sensibly defaulted. The README documents each option with runnable snippets and live JSFiddle demos, and the script-tag attachment mode gives non-bundler users a zero-JavaScript path in. The main rough edge is that the eventify-based .on() API returns no unsubscribe handle.
Used by 3 apps in this directory
Open WebUI
AI Assistants · AI Agents
The extensible, privacy-first AI platform that runs Ollama, OpenAI, and any LLM backend behind a polished, feature-packed web interface.
Windmill
Automation · Developer Tools
Turn scripts into webhooks, workflows, and auto-generated UIs — the fastest self-hostable workflow engine, 13x faster than Airflow.
Zulip
Team Chat
Topic-based team chat that brings the structure of email threads to real-time messaging, so distributed teams never lose context across hundreds of concurrent conversations.