@beoe/pan-zoom
Small JS library to add pan and zoom functionality to SVG (inline or image). It supports gestures for all types of devices:
| intention | mouse | trackpad/touchpad | touchscren |
|---|---|---|---|
| pan | click + move | click + move | two finger drag |
| zoom | Ctrl + wheel | pinch | pinch |
| reset | double click | double click | double tap |
| scroll | wheel | two finger drag | one finger drag |
Pay attention:
- gestures intentionally selected to not interfere with the system’s default scroll gestures, to avoid “scroll traps”
- all actions are available through gestures, so it works without UI. You can add UI, though. Library exposes methods for this, like
pan(dx, dy)andzoom(scale) - Cmd + click - zoom in
- Alt + click - zoom out
- First double click (tap) - zoom in x2
Usage
There are two flavors:
- Headless - without UI
- Default UI
Headless
If you have container element in HTML:
import { PanZoom } from "@beoe/pan-zoom";
document.querySelectorAll(".beoe").forEach((container) => { const element = container.firstElementChild; if (!element) return; new PanZoom({ element, container }).on();});If you don’t have container element in HTML:
import { PanZoom } from "@beoe/pan-zoom";
document.querySelectorAll("svg, img[src$='.svg' i]").forEach((element) => { if (element.parentElement?.tagName === "PICTURE") { element = element.parentElement; } const container = document.createElement("div"); container.className = "beoe"; element.replaceWith(container); container.append(element); new PanZoom({ element, container }).on();});Additionally following CSS is required:
.beoe { overflow: hidden; touch-action: pan-x pan-y; user-select: none; cursor: grab;}
.beoe svg,.beoe img { /* need to center smaller images to fix bug in zoom functionality */ margin: auto; display: block; /* need to fit bigger images */ max-width: 100%; height: auto;}
.beoe img { pointer-events: none;}Instance methods:
on()- adds event listenersoff()- removes event listenerspan(dx, dy)- pans imagezoom(scale)- zooms imagereset()- resets pan and zoom values
Default UI
import "@beoe/pan-zoom/css/PanZoomUi.css";import { PanZoomUi } from "@beoe/pan-zoom";
document.querySelectorAll(".beoe").forEach((container) => { const element = container.firstElementChild; if (!element) return; new PanZoomUi({ element, container }).on();});Additionally, CSS to style UI required (for example with Tailwind):
.beoe .buttons { @apply inline-flex;}.beoe .zoom-in { @apply bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded-l;}.beoe .reset { @apply bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4;}.beoe .zoom-out { @apply bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded-r;}You can configure HTML classes used by UI:
const classes = { zoomIn: "zoom-in", reset: "reset", zoomOut: "zoom-out", buttons: "buttons", tsWarning: "touchscreen-warning", tsWarningActive: "active",};
new PanZoomUi({ element, container, classes });and message with instructions for the touchscreen:
const message = "Use two fingers to pan and zoom";
new PanZoomUi({ element, container, message });Pixelation in Safari
Be aware that some CSS will cause pixelation of SVG on zoom (bug in Safari), for example:
will-change: transform;transform: matrix3d(...);transition-property: transform;(it setles after animation, though)
TODO
- Show UI for 5-10 seconds after cursor move, then hide it. Like controls in video player
- Do not stretch images if they are smaller than viewport
- Do not show PanZoom UI for small images
- Prevent clicks on drag or pan
- minimap and full-screen mode, like in reactflow
- Create a Rehype plugin to wrap images in a container (
<figure class="beoe"></figure>) to avoid creating it on the client side.