@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.