@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 ADDED
@@ -0,0 +1,145 @@
1
+ ![useZoomPan Banner](.github/assets/banner.webp)
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)
@@ -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 };
@@ -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 };