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

@sitebytom/use-zoom-pan

v1.0.5

Published

React hook and component for smooth zoom and pan interactions on any content with full mouse and touch support

Readme

useZoomPan Banner

useZoomPan

A zero-dependency, ultra-lightweight React hook and component for implementing smooth, high-performance zoom and pan interactions. Ideal for image viewers, galleries, maps, diagrams, and custom interactive canvases where you want full control without heavy dependencies.

Live Demo & Documentation

Documentation

Interactive Examples

Features

  • Ultra-Lightweight: Zero dependencies, minimal bundle size.
  • High Performance: Uses ref-based interaction tracking and minimal state updates to keep zoom and pan interactions smooth and responsive.
  • Mouse Controls: Scroll to zoom at cursor, click to toggle zoom, drag to pan.
  • Touch Controls: Pinch to zoom at center, double-tap to zoom/reset, swipe to navigate.
  • Keyboard Controls: Zoom with +/-, pan with arrows, and instant reset with Esc.
  • Precision Focal Math: Implemented with a top-left origin for rock-solid cursor tracking during zoom.
  • Smart Bounds: Prevents over-panning with configurable buffer zones and symmetrical clamping.
  • Mobile First: Optimized touch thresholds, double-tap gestures, and native-feeling interactions.
  • Content Agnostic: Works with images, SVG, canvas, or any HTML content.

Quick Start

Installation

pnpm add @sitebytom/use-zoom-pan

Component Approach

Wraps any content and handles the standard zoom/pan logic automatically.

import { ZoomPan } from '@sitebytom/use-zoom-pan'

const MyViewer = () => (
  <div style={{ width: '100%', height: '500px' }}>
    <ZoomPan>
      <img src="/my-image.jpg" alt="Zoomable" />
    </ZoomPan>
  </div>
)

Hook Approach

For custom UI layouts where you need access to the raw scale and position state.

import { useRef } from 'react'
import { useZoomPan } from '@sitebytom/use-zoom-pan'

const MyCustomViewer = () => {
  const containerRef = useRef<HTMLDivElement>(null)
  const { scale, position, contentProps, containerProps } = useZoomPan({ containerRef })

  return (
    <div 
      ref={containerRef} 
      {...containerProps}
      style={{ width: '100%', height: '500px', overflow: 'hidden' }}
    >
      <img 
        {...contentProps}
        src="/image.jpg" 
        style={{ transform: `translate(${position.x}px, ${position.y}px) scale(${scale})` }} 
      />
    </div>
  )
}

Controlled vs. Uncontrolled: The hook manages zoom and pan state internally but exposes scale and position for read-only inspection or custom UI overlays.

Automatic Event Management

Spread containerProps onto your container element if you want the hook to manage container-level pointer events (like scroll-to-zoom and dragging) automatically.

API Reference

Component Props (<ZoomPan />)

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | Required | The content to make zoomable. | | enableZoom | boolean | true | Enable/disable zoom functionality. | | onNext / onPrev | () => void | - | Callbacks for swipe-based navigation. | | options | ZoomPanOptions | - | Configuration overrides (see Options below). | | className | string | - | Additional CSS class for the container. | | style | CSSProperties | - | Inline styles for the container. | | contentClassName | string | - | CSS class for the inner content wrapper. | | contentStyle | CSSProperties | - | Inline styles for the inner content wrapper. |

Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | minScale | number | 1 | Minimum zoom level. | | maxScale | number | 6 | Maximum zoom level. | | zoomSensitivity | number | 0.002 | Scaling multiplier for scroll wheel. | | clickZoomScale | number | 2.5 | Snap-to scale on double click/tap. | | dragThresholdTouch | number | 10 | Pixels to move before panning triggers (touch). | | dragThresholdMouse | number | 5 | Pixels to move before panning triggers (mouse). | | swipeThreshold | number | 50 | Pixels to move before swipe navigation triggers. | | boundsBuffer | number | 80 | Extra panning room beyond content edges. | | initialScale | number | - | Initial zoom level. | | initialPosition | Position | - | Initial x/y coordinates. | | manageCursor | boolean | true | Automatically handle zoom-in/grab cursor states. | | enableSwipe | boolean | true | Enable/disable swipe gestures for navigation. |

Hook Return Value

The useZoomPan hook returns an object containing the current state and necessary event handlers.

| Value | Type | Description | |-------|------|-------------| | scale | number | Current zoom level (1-4 by default). | | position | object | Current pan coordinates { x, y }. | | isDragging | boolean | True when the user is actively panning. | | reset | function | Resets zoom and pan to centered defaults. | | zoomTo | function | Imperative zoom to (x, y, scale) in container coords. | | contentProps | object | Event handlers to spread on the zoomable content. | | containerProps | object | Event handlers to spread on the container element. |

Interaction Guide

Mouse Controls

  • Scroll Wheel: Zoom in/out at cursor position.
  • Left Click: Toggle zoom level (1x ⟷ 2.5x).
  • Drag: Pan the content when zoomed in.

Touch Controls

  • Pinch: Smooth scale at the center of the pinch.
  • Double Tap: Toggle zoom level.
  • Swipe: Navigate between content (if onNext/onPrev provided).

Keyboard Controls

  • + or =: Zoom in at the center of the viewport.
  • - or _: Zoom out from the center of the viewport.
  • Arrow Keys: Pan the content in any direction while zoomed.
  • Escape: Instantly reset zoom and position to default.

Performance Tuning

The hook is designed to be highly optimized for smooth interactions on high-resolution displays and mobile devices. To maintain responsiveness without taxing the main thread, follow these best practices:

  • will-change: Apply will-change: transform to your content element to promote it to its own GPU layer.
  • Avoid Transitions: Do not use CSS transitions for the transform property while the user is actively dragging; the hook handles position updates via JS for maximum responsiveness.
  • Touch Action: Add touch-action: none to your viewport to prevent browser-native scrolling while interacting with the component.
  • Optimized state updates: useZoomPan minimizes React work during interactions by tracking gesture state in refs and only committing state when visual output changes.

Design Philosophy

The hook avoids continuous requestAnimationFrame loops and animation libraries, relying instead on native pointer events and direct transform updates for a predictable, lightweight, and low-latency experience.