npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@riky1/svg-panzoom

v1.1.8

Published

Framework-agnostic SVG zoom & pan for inline SVG elements.

Downloads

1,584

Readme

svg-panzoom

Framework-agnostic library (JavaScript ES Modules) for adding pan (drag), zoom (wheel), and pinch zoom (touch) to inline SVG in the DOM.

Goal: framework-free core, simple API, built as an npm package.

Status: v1.1.8 — Stable release with source code transparency, frozen API, TypeScript support, and comprehensive test coverage.

Demo: https://riky1.github.io/svg-panzoom/

Install

npm i @riky1/svg-panzoom

Source Code Transparency

The full source code is included in every npm package for security verification and audit purposes:

  • Source code available: All source files (src/) are distributed with the package
  • Public repository: Code available at https://github.com/riky1/svg-panzoom for independent verification
  • Security verification: Security scanners (e.g., socket.dev) can verify package integrity and source transparency
  • MIT Licensed: Open-source license for complete transparency and community trust

Usage

Vanilla (any framework)

import { createSvgPanZoom } from '@riky1/svg-panzoom';
import '@riky1/svg-panzoom/style.css';

const instance = createSvgPanZoom({
  element: document.querySelector('#myContainerOrSvg'),
  viewportSelector: '[data-spz-viewport]', // optional
  minZoom: 0.4,
  maxZoom: 6,
  zoomStep: 1.2,
  wheelZoom: true,
  panEnabled: true,
  bounds: { enabled: true, padding: 20 },
  fitOnInit: true,
  centerOnInit: true
});

📁 Full example: examples/basic/

Vue 3 (Composition API)

<template>
  <div ref="containerRef" style="width: 100%; height: 100%">
    <svg viewBox="0 0 800 450" width="100%" height="100%">
      <g data-spz-viewport="true">
        <!-- SVG content -->
      </g>
    </svg>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { createSvgPanZoom } from '@riky1/svg-panzoom';
import '@riky1/svg-panzoom/style.css';

const containerRef = ref(null);
const instance = ref(null);

onMounted(() => {
  instance.value = createSvgPanZoom({
    element: containerRef.value,
    viewportSelector: '[data-spz-viewport]'
  });
});

onBeforeUnmount(() => {
  instance.value?.destroy();
});
</script>

📁 Full example: examples/vue/SvgPanZoomDemo.vue

React (Hooks)

import { useRef, useEffect } from 'react';
import { createSvgPanZoom } from '@riky1/svg-panzoom';
import '@riky1/svg-panzoom/style.css';

export default function SvgPanZoom() {
  const containerRef = useRef(null);
  const instanceRef = useRef(null);

  useEffect(() => {
    instanceRef.current = createSvgPanZoom({
      element: containerRef.current,
      viewportSelector: '[data-spz-viewport]'
    });

    return () => {
      instanceRef.current?.destroy();
    };
  }, []);

  return (
    <div ref={containerRef} style={{ width: '100%', height: '100%' }}>
      <svg viewBox="0 0 800 450" width="100%" height="100%">
        <g data-spz-viewport="true">
          {/* SVG content */}
        </g>
      </svg>
    </div>
  );
}

📁 Full example: examples/react/SvgPanZoomDemo.jsx

Common API Usage

Once the instance is created (using any of the methods above), you can use the following methods and listen to events:

// Methods
instance.zoomIn();
instance.zoomOut();
instance.zoomTo(2);
instance.panBy(10, 0);
instance.panTo(0, 0);
instance.reset();
instance.fit();
instance.center();

// Events
const off = instance.on('change', (state) => console.log(state));
off(); // unsubscribe

// Cleanup
instance.destroy();

Recommended markup (viewport)

To apply the transformation to an internal group add data-spz-viewport="true" to the first <g> element:

<svg viewBox="0 0 800 450">
  <g data-spz-viewport="true">
    <!-- content -->
  </g>
</svg>

If viewportSelector is not provided, the library tries to use:

  1. [data-spz-viewport]
  2. the first <g>
  3. otherwise creates a <g data-spz-viewport> and moves elements inside (excluding <defs>).

API

Factory:

  • createSvgPanZoom(options)

Instance methods:

  • zoomIn(origin?)
  • zoomOut(origin?)
  • zoomTo(scale, origin?)
  • panBy(dx, dy)
  • panTo(x, y)
  • reset()
  • fit()
  • center()
  • getState()
  • getOptions()
  • on(event, callback) → returns an off() function
  • off(event, callback)
  • destroy()

Options

  • element (required) Element | SVGSVGElement: container or inline svg
  • viewportSelector string | null: selector of the viewport (<g>) to apply the transform
  • minZoom number (default 0.0001)
  • maxZoom number (default 10)
  • initialZoom number (default 1)
  • zoomStep number (default 1.25)
  • zoomDuration number (default 200)
  • zoomInertia boolean (default true)
  • zoomInertiaDuration number (default 600)
  • wheelZoomIntensity number (default 0.003)
  • wheelZoom boolean (default true)
  • panEnabled boolean (default true)
  • pinchZoom boolean (default true)
  • inertiaPan boolean (default true)
  • inertiaDuration number (default 300)
  • inertiaFriction number (default 0.92)
  • bounds { enabled: boolean, padding: number, overflow?: number | boolean } (default {enabled:true,padding:0,overflow:0})
  • fitOnInit boolean (default false)
  • centerOnInit boolean (default false)

Events

Custom events emitted by the instance:

  • change{ scale, x, y, dragging, size }
  • zoom{ scale }
  • reset → state
  • fit → state
  • center → state
  • measure → updated measurements
  • dragstart / drag / dragend
  • wheel

Understanding Coordinates

State coordinates (x, y, scale)

All coordinates returned by getState() and passed to events use SVG user units (the viewBox coordinate system), not screen pixels.

const state = instance.getState();
console.log(state);
// {
//   scale: 1.5,        // zoom level (1 = 100%, 2 = 200%)
//   x: 100,            // pan position in SVG user units (viewBox space)
//   y: 50,             // pan position in SVG user units (viewBox space)
//   dragging: false,   // is pointer actively dragging
//   size: { width: 800, height: 450 }  // current viewport dimensions (screen pixels)
// }

Important: x and y represent the position of the top-left corner of the viewport in SVG coordinates. When you use methods like panTo(100, 50), you're moving the viewport to show SVG position (100, 50) at the top-left.

Bounds behavior

Bounds are calculated in screen-space and applied as min/max pan limits. The padding option adds a margin around the viewable content:

  • bounds.enabled: true — enforces min/max pan boundaries
  • bounds.padding: 20 — adds 20px margin around content edges
  • bounds.overflow: 50 or true — allows content to exit viewport by specified pixels or completely

When bounds are tight, panning is prevented from moving outside content; with overflow, you can "overshoot" and see blank space.

Limitations & Browser Support

Supported targets

  • Inline SVG in the DOM (recommended: with <g data-spz-viewport> or first <g> as viewport)
  • External SVG files (loaded via <object> or <iframe>)
  • SVG images (loaded via <img>)

Browser compatibility

Tested and supported:

  • Chrome/Edge (latest)
  • Firefox (latest)
  • Safari (latest)
  • iOS Safari (latest)

Known limitations and planned features

  • DOM restoration: When the library creates a <g data-spz-viewport> automatically, calling destroy() does not restore the original DOM structure. Workaround: provide an explicit viewportSelector if you need precise control.
  • Pinch zoom: Two-finger pinch gesture is now supported on touch devices. Requires Pointer Events support and inline SVG in the DOM. Can be disabled with pinchZoom: false.
  • Custom easing: Animation uses linear interpolation; custom easing functions are not yet supported (planned for v2).
  • Bounds: Current bounds implementation is MVP (screen-space only). Advanced bounds with rotation or skew are not supported.
  • Mouse wheel normalization: Wheel event delta normalization is minimal (supports deltaMode 0/1/2). Advanced wheel behaviors may vary across browsers.

Cleanup and memory

Always call destroy() when the instance is no longer needed (e.g., in component unmount handlers):

// Vue
onBeforeUnmount(() => instance.value?.destroy());

// React
useEffect(() => {
  return () => instanceRef.current?.destroy();
}, []);

destroy() is idempotent and safe to call multiple times. It removes all listeners, observers, and animation loops to prevent memory leaks.

Dev

npm install
npm run dev

Open the example: examples/basic/index.html (Vite in dev mode also serves SCSS imports).

Build (library)

npm run build

Output:

  • dist/svg-panzoom.js (ESM)
  • dist/svg-panzoom.cjs (CJS)
  • dist/style.css (compiled CSS)

Notes / future improvements

  • Bounds: currently simple and in screen-space (MVP).
  • DOM restoration: if a viewport <g> is created, calling destroy() does not restore the original structure (v2).
  • React/Vue wrappers
  • Optional UI controls
  • Advanced animations / easing

License

MIT