@sitebytom/use-zoom-pan 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -0
- package/dist/index.d.mts +103 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +648 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +641 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# useZoomPan
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
[**Live Demo & Technical Reference**](https://sitebytom.github.io/use-zoom-pan/)
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Ultra-Lightweight**: Zero dependencies, minimal bundle size.
|
|
12
|
+
- **High Performance**: Uses ref-based interaction tracking and minimal state updates to keep zoom and pan interactions smooth and responsive.
|
|
13
|
+
- **Mouse Controls**: Scroll to zoom at cursor, click to toggle zoom, drag to pan.
|
|
14
|
+
- **Touch Controls**: Pinch to zoom at center, double tap to toggle, swipe to navigate.
|
|
15
|
+
- **Smart Bounds**: Prevents over-panning with configurable buffer zones.
|
|
16
|
+
- **Mobile First**: Optimized touch thresholds and native-feeling gestures.
|
|
17
|
+
- **Content Agnostic**: Works with images, SVG, canvas, or any HTML content.
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add @sitebytom/use-zoom-pan
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Component Approach
|
|
28
|
+
Wraps any content and handles the standard zoom/pan logic automatically.
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { ZoomPan } from '@sitebytom/use-zoom-pan'
|
|
32
|
+
|
|
33
|
+
const MyViewer = () => (
|
|
34
|
+
<div style={{ width: '100%', height: '500px' }}>
|
|
35
|
+
<ZoomPan>
|
|
36
|
+
<img src="/my-image.jpg" alt="Zoomable" />
|
|
37
|
+
</ZoomPan>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Hook Approach
|
|
43
|
+
For custom UI layouts where you need access to the raw `scale` and `position` state.
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { useRef } from 'react'
|
|
47
|
+
import { useZoomPan } from '@sitebytom/use-zoom-pan'
|
|
48
|
+
|
|
49
|
+
const MyCustomViewer = () => {
|
|
50
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
51
|
+
const { scale, position, contentProps, containerProps } = useZoomPan({ containerRef })
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
ref={containerRef}
|
|
56
|
+
{...containerProps}
|
|
57
|
+
style={{ width: '100%', height: '500px', overflow: 'hidden' }}
|
|
58
|
+
>
|
|
59
|
+
<img
|
|
60
|
+
{...contentProps}
|
|
61
|
+
src="/image.jpg"
|
|
62
|
+
style={{ transform: `translate(${position.x}px, ${position.y}px) scale(${scale})` }}
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
> **Controlled vs. Uncontrolled**: The hook manages zoom and pan state internally but exposes `scale` and `position` for read-only inspection or custom UI overlays.
|
|
70
|
+
|
|
71
|
+
### Automatic Event Management
|
|
72
|
+
Spread `containerProps` onto your container element if you want the hook to manage container-level pointer events (like scroll-to-zoom and dragging) automatically.
|
|
73
|
+
|
|
74
|
+
## API Reference
|
|
75
|
+
|
|
76
|
+
### Component Props (`<ZoomPan />`)
|
|
77
|
+
|
|
78
|
+
| Prop | Type | Default | Description |
|
|
79
|
+
|------|------|---------|-------------|
|
|
80
|
+
| `children` | `ReactNode` | Required | The content to make zoomable. |
|
|
81
|
+
| `enableZoom` | `boolean` | `true` | Enable/disable zoom functionality. |
|
|
82
|
+
| `onNext / onPrev` | `() => void` | - | Callbacks for swipe-based navigation. |
|
|
83
|
+
| `options` | `ZoomPanOptions` | - | Configuration overrides (see Options below). |
|
|
84
|
+
| `className` | `string` | - | Additional CSS class for the container. |
|
|
85
|
+
|
|
86
|
+
### Configuration Options
|
|
87
|
+
|
|
88
|
+
| Option | Type | Default | Description |
|
|
89
|
+
|--------|------|---------|-------------|
|
|
90
|
+
| `minScale` | `number` | `1` | Minimum zoom level. |
|
|
91
|
+
| `maxScale` | `number` | `4` | Maximum zoom level. |
|
|
92
|
+
| `zoomSensitivity` | `number` | `0.002` | Scaling multiplier for scroll wheel. |
|
|
93
|
+
| `clickZoomScale` | `number` | `2.5` | Snap-to scale on double click/tap. |
|
|
94
|
+
| `dragThresholdTouch` | `number` | `10` | Pixels to move before panning triggers (touch). |
|
|
95
|
+
| `dragThresholdMouse` | `number` | `5` | Pixels to move before panning triggers (mouse). |
|
|
96
|
+
| `swipeThreshold` | `number` | `50` | Pixels to move before swipe navigation triggers. |
|
|
97
|
+
| `boundsBuffer` | `number` | `80` | Extra panning room beyond content edges. |
|
|
98
|
+
| `initialScale` | `number` | - | Initial zoom level. |
|
|
99
|
+
| `initialPosition` | `Position` | - | Initial x/y coordinates. |
|
|
100
|
+
| `manageCursor` | `boolean` | `true` | Automatically handle zoom-in/grab cursor states. |
|
|
101
|
+
| `enableSwipe` | `boolean` | `true` | Enable/disable swipe gestures for navigation. |
|
|
102
|
+
|
|
103
|
+
### Hook Return Value
|
|
104
|
+
|
|
105
|
+
The `useZoomPan` hook returns an object containing the current state and necessary event handlers.
|
|
106
|
+
|
|
107
|
+
| Value | Type | Description |
|
|
108
|
+
|-------|------|-------------|
|
|
109
|
+
| `scale` | `number` | Current zoom level (1-4 by default). |
|
|
110
|
+
| `position` | `object` | Current pan coordinates `{ x, y }`. |
|
|
111
|
+
| `isDragging` | `boolean` | True when the user is actively panning. |
|
|
112
|
+
| `reset` | `function` | Resets zoom and pan to defaults. |
|
|
113
|
+
| `contentProps` | `object` | Event handlers to spread on the zoomable content. |
|
|
114
|
+
| `containerProps` | `object` | Event handlers to spread on the container element. |
|
|
115
|
+
|
|
116
|
+
## Interaction Guide
|
|
117
|
+
|
|
118
|
+
### Mouse Controls
|
|
119
|
+
- **Scroll Wheel**: Zoom in/out at cursor position.
|
|
120
|
+
- **Left Click**: Toggle zoom level (1x ⟷ 2.5x).
|
|
121
|
+
- **Drag**: Pan the content when zoomed in.
|
|
122
|
+
|
|
123
|
+
### Touch Controls
|
|
124
|
+
- **Pinch**: Smooth scale at the center of the pinch.
|
|
125
|
+
- **Double Tap**: Toggle zoom level.
|
|
126
|
+
- **Swipe**: Navigate between content (if `onNext`/`onPrev` provided).
|
|
127
|
+
|
|
128
|
+
## Performance Tuning
|
|
129
|
+
|
|
130
|
+
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:
|
|
131
|
+
|
|
132
|
+
- **will-change**: Apply `will-change: transform` to your content element to promote it to its own GPU layer.
|
|
133
|
+
- **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.
|
|
134
|
+
- **Touch Action**: Add `touch-action: none` to your viewport to prevent browser-native scrolling while interacting with the component.
|
|
135
|
+
- **Optimized state updates**: `useZoomPan` minimizes React work during interactions by tracking gesture state in refs and only committing state when visual output changes.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### Design Philosophy
|
|
140
|
+
|
|
141
|
+
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.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
[@sitebytom](https://github.com/sitebytom)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React, { CSSProperties } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Position {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
interface ZoomPanOptions {
|
|
8
|
+
/** Minimum zoom scale (default: 1) */
|
|
9
|
+
minScale?: number;
|
|
10
|
+
/** Maximum zoom scale (default: 4) */
|
|
11
|
+
maxScale?: number;
|
|
12
|
+
/** Initial zoom scale (default: minScale) */
|
|
13
|
+
initialScale?: number;
|
|
14
|
+
/** Initial x/y position (default: {x:0, y:0}) */
|
|
15
|
+
initialPosition?: Position;
|
|
16
|
+
/** Mouse wheel zoom sensitivity (default: 0.002) */
|
|
17
|
+
zoomSensitivity?: number;
|
|
18
|
+
/** Zoom level when clicking to zoom (default: 2.5) */
|
|
19
|
+
clickZoomScale?: number;
|
|
20
|
+
/** Pixels moved before drag is detected on mouse (default: 5) */
|
|
21
|
+
dragThresholdMouse?: number;
|
|
22
|
+
/** Pixels moved before drag is detected on touch (default: 10) */
|
|
23
|
+
dragThresholdTouch?: number;
|
|
24
|
+
/** Pixels swiped before navigation triggers (default: 50) */
|
|
25
|
+
swipeThreshold?: number;
|
|
26
|
+
/** Extra pan space beyond image edges in pixels (default: 80) */
|
|
27
|
+
boundsBuffer?: number;
|
|
28
|
+
/** Whether to automatically manage cursor states (default: true) */
|
|
29
|
+
manageCursor?: boolean;
|
|
30
|
+
/** Whether to enable swipe navigation (default: true) */
|
|
31
|
+
enableSwipe?: boolean;
|
|
32
|
+
}
|
|
33
|
+
interface ZoomPanProps$1 {
|
|
34
|
+
/** Reference to the container element (div, section, etc.) */
|
|
35
|
+
containerRef: React.RefObject<HTMLElement | null>;
|
|
36
|
+
enableZoom?: boolean;
|
|
37
|
+
onNext?: () => void;
|
|
38
|
+
onPrev?: () => void;
|
|
39
|
+
options?: ZoomPanOptions;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A highly optimized hook for zoom and pan interactions.
|
|
43
|
+
* Supports mouse wheel, dragging, double-click to focal zoom, and pinch-to-zoom on touch.
|
|
44
|
+
*/
|
|
45
|
+
declare const useZoomPan: ({ containerRef, enableZoom, onNext, onPrev, options }: ZoomPanProps$1) => {
|
|
46
|
+
scale: number;
|
|
47
|
+
position: Position;
|
|
48
|
+
isDragging: boolean;
|
|
49
|
+
reset: () => void;
|
|
50
|
+
zoomTo: (x: number, y: number, targetScale?: number) => void;
|
|
51
|
+
contentProps: {
|
|
52
|
+
ref: React.Ref<any>;
|
|
53
|
+
style: React.CSSProperties;
|
|
54
|
+
onClick: (e: React.MouseEvent<HTMLImageElement>) => void;
|
|
55
|
+
onDoubleClick: (e: React.MouseEvent<HTMLImageElement>) => void;
|
|
56
|
+
onTouchStart: (e: React.TouchEvent<HTMLImageElement>) => void;
|
|
57
|
+
onTouchMove: (e: React.TouchEvent<HTMLImageElement>) => void;
|
|
58
|
+
onTouchEnd: () => void;
|
|
59
|
+
onMouseDown: (e: React.MouseEvent<HTMLImageElement>) => void;
|
|
60
|
+
onMouseMove: (e: React.MouseEvent<HTMLImageElement>) => void;
|
|
61
|
+
onMouseUp: () => void;
|
|
62
|
+
onMouseLeave: () => void;
|
|
63
|
+
};
|
|
64
|
+
containerProps: {
|
|
65
|
+
onTouchStart: (e: React.TouchEvent) => void;
|
|
66
|
+
onTouchEnd: (e: React.TouchEvent) => void;
|
|
67
|
+
onMouseDown: (e: React.MouseEvent) => void;
|
|
68
|
+
onMouseUp: (e: React.MouseEvent) => void;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
interface ZoomPanProps {
|
|
73
|
+
children: React.ReactNode;
|
|
74
|
+
className?: string;
|
|
75
|
+
style?: CSSProperties;
|
|
76
|
+
contentClassName?: string;
|
|
77
|
+
contentStyle?: CSSProperties;
|
|
78
|
+
enableZoom?: boolean;
|
|
79
|
+
onNext?: () => void;
|
|
80
|
+
onPrev?: () => void;
|
|
81
|
+
options?: ZoomPanOptions;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A simple component wrapper for zoom and pan functionality.
|
|
85
|
+
* Wrap any content (images, SVG, canvas, etc.) to make it zoomable and pannable.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```tsx
|
|
89
|
+
* <ZoomPan>
|
|
90
|
+
* <img src="photo.jpg" alt="Zoomable" />
|
|
91
|
+
* </ZoomPan>
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @example With custom options
|
|
95
|
+
* ```tsx
|
|
96
|
+
* <ZoomPan options={{ maxScale: 6, clickZoomScale: 3 }}>
|
|
97
|
+
* <canvas ref={canvasRef} />
|
|
98
|
+
* </ZoomPan>
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
declare const ZoomPan: React.FC<ZoomPanProps>;
|
|
102
|
+
|
|
103
|
+
export { ZoomPan, type ZoomPanOptions, useZoomPan };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React, { CSSProperties } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Position {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
interface ZoomPanOptions {
|
|
8
|
+
/** Minimum zoom scale (default: 1) */
|
|
9
|
+
minScale?: number;
|
|
10
|
+
/** Maximum zoom scale (default: 4) */
|
|
11
|
+
maxScale?: number;
|
|
12
|
+
/** Initial zoom scale (default: minScale) */
|
|
13
|
+
initialScale?: number;
|
|
14
|
+
/** Initial x/y position (default: {x:0, y:0}) */
|
|
15
|
+
initialPosition?: Position;
|
|
16
|
+
/** Mouse wheel zoom sensitivity (default: 0.002) */
|
|
17
|
+
zoomSensitivity?: number;
|
|
18
|
+
/** Zoom level when clicking to zoom (default: 2.5) */
|
|
19
|
+
clickZoomScale?: number;
|
|
20
|
+
/** Pixels moved before drag is detected on mouse (default: 5) */
|
|
21
|
+
dragThresholdMouse?: number;
|
|
22
|
+
/** Pixels moved before drag is detected on touch (default: 10) */
|
|
23
|
+
dragThresholdTouch?: number;
|
|
24
|
+
/** Pixels swiped before navigation triggers (default: 50) */
|
|
25
|
+
swipeThreshold?: number;
|
|
26
|
+
/** Extra pan space beyond image edges in pixels (default: 80) */
|
|
27
|
+
boundsBuffer?: number;
|
|
28
|
+
/** Whether to automatically manage cursor states (default: true) */
|
|
29
|
+
manageCursor?: boolean;
|
|
30
|
+
/** Whether to enable swipe navigation (default: true) */
|
|
31
|
+
enableSwipe?: boolean;
|
|
32
|
+
}
|
|
33
|
+
interface ZoomPanProps$1 {
|
|
34
|
+
/** Reference to the container element (div, section, etc.) */
|
|
35
|
+
containerRef: React.RefObject<HTMLElement | null>;
|
|
36
|
+
enableZoom?: boolean;
|
|
37
|
+
onNext?: () => void;
|
|
38
|
+
onPrev?: () => void;
|
|
39
|
+
options?: ZoomPanOptions;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A highly optimized hook for zoom and pan interactions.
|
|
43
|
+
* Supports mouse wheel, dragging, double-click to focal zoom, and pinch-to-zoom on touch.
|
|
44
|
+
*/
|
|
45
|
+
declare const useZoomPan: ({ containerRef, enableZoom, onNext, onPrev, options }: ZoomPanProps$1) => {
|
|
46
|
+
scale: number;
|
|
47
|
+
position: Position;
|
|
48
|
+
isDragging: boolean;
|
|
49
|
+
reset: () => void;
|
|
50
|
+
zoomTo: (x: number, y: number, targetScale?: number) => void;
|
|
51
|
+
contentProps: {
|
|
52
|
+
ref: React.Ref<any>;
|
|
53
|
+
style: React.CSSProperties;
|
|
54
|
+
onClick: (e: React.MouseEvent<HTMLImageElement>) => void;
|
|
55
|
+
onDoubleClick: (e: React.MouseEvent<HTMLImageElement>) => void;
|
|
56
|
+
onTouchStart: (e: React.TouchEvent<HTMLImageElement>) => void;
|
|
57
|
+
onTouchMove: (e: React.TouchEvent<HTMLImageElement>) => void;
|
|
58
|
+
onTouchEnd: () => void;
|
|
59
|
+
onMouseDown: (e: React.MouseEvent<HTMLImageElement>) => void;
|
|
60
|
+
onMouseMove: (e: React.MouseEvent<HTMLImageElement>) => void;
|
|
61
|
+
onMouseUp: () => void;
|
|
62
|
+
onMouseLeave: () => void;
|
|
63
|
+
};
|
|
64
|
+
containerProps: {
|
|
65
|
+
onTouchStart: (e: React.TouchEvent) => void;
|
|
66
|
+
onTouchEnd: (e: React.TouchEvent) => void;
|
|
67
|
+
onMouseDown: (e: React.MouseEvent) => void;
|
|
68
|
+
onMouseUp: (e: React.MouseEvent) => void;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
interface ZoomPanProps {
|
|
73
|
+
children: React.ReactNode;
|
|
74
|
+
className?: string;
|
|
75
|
+
style?: CSSProperties;
|
|
76
|
+
contentClassName?: string;
|
|
77
|
+
contentStyle?: CSSProperties;
|
|
78
|
+
enableZoom?: boolean;
|
|
79
|
+
onNext?: () => void;
|
|
80
|
+
onPrev?: () => void;
|
|
81
|
+
options?: ZoomPanOptions;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A simple component wrapper for zoom and pan functionality.
|
|
85
|
+
* Wrap any content (images, SVG, canvas, etc.) to make it zoomable and pannable.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```tsx
|
|
89
|
+
* <ZoomPan>
|
|
90
|
+
* <img src="photo.jpg" alt="Zoomable" />
|
|
91
|
+
* </ZoomPan>
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @example With custom options
|
|
95
|
+
* ```tsx
|
|
96
|
+
* <ZoomPan options={{ maxScale: 6, clickZoomScale: 3 }}>
|
|
97
|
+
* <canvas ref={canvasRef} />
|
|
98
|
+
* </ZoomPan>
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
declare const ZoomPan: React.FC<ZoomPanProps>;
|
|
102
|
+
|
|
103
|
+
export { ZoomPan, type ZoomPanOptions, useZoomPan };
|