@@ -6,11 +6,21 @@ export default function usePanZoom(svgRef, initialViewBox = { x: 0, y: 0, width:
6
6
const scale = ref ( 1 ) ;
7
7
const isPanning = ref ( false ) ;
8
8
const startPoint = ref ( { x : 0 , y : 0 } ) ;
9
+ const pinchStartDist = ref ( 0 ) ;
10
+ const pinchStartViewBox = ref ( null ) ;
11
+ const isPinching = ref ( false ) ;
9
12
10
13
let velocity = { x : 0 , y : 0 } ;
11
14
let animationFrame = null ;
12
15
let zoomAnimationFrame = null ;
13
16
17
+ function getTouchDistance ( touches ) {
18
+ if ( touches . length < 2 ) return 0 ;
19
+ const dx = touches [ 0 ] . clientX - touches [ 1 ] . clientX ;
20
+ const dy = touches [ 0 ] . clientY - touches [ 1 ] . clientY ;
21
+ return Math . sqrt ( dx * dx + dy * dy ) ;
22
+ }
23
+
14
24
function toSvgPoint ( event ) {
15
25
const svg = svgRef . value ;
16
26
if ( ! svg ) return { x : 0 , y : 0 } ;
@@ -119,6 +129,44 @@ export default function usePanZoom(svgRef, initialViewBox = { x: 0, y: 0, width:
119
129
scale . value = newScale ;
120
130
} ;
121
131
132
+ function handleTouchStart ( event ) {
133
+ if ( event . touches . length === 2 ) {
134
+ isPinching . value = true ;
135
+ pinchStartDist . value = getTouchDistance ( event . touches ) ;
136
+ pinchStartViewBox . value = { ...viewBox . value } ;
137
+ } else {
138
+ event . preventDefault ( ) ;
139
+ startPan ( event ) ;
140
+ }
141
+ }
142
+
143
+ function handleTouchMove ( event ) {
144
+ if ( isPinching . value && event . touches . length === 2 ) {
145
+ event . preventDefault ( ) ;
146
+ const dist = getTouchDistance ( event . touches ) ;
147
+ if ( pinchStartDist . value ) {
148
+ const zoomFactor = dist / pinchStartDist . value ;
149
+ const svg = svgRef . value ;
150
+ const rect = svg . getBoundingClientRect ( ) ;
151
+ const midX = ( event . touches [ 0 ] . clientX + event . touches [ 1 ] . clientX ) / 2 - rect . left ;
152
+ const midY = ( event . touches [ 0 ] . clientY + event . touches [ 1 ] . clientY ) / 2 - rect . top ;
153
+ const midPoint = toSvgPoint ( { clientX : midX + rect . left , clientY : midY + rect . top } ) ;
154
+ viewBox . value = { ...pinchStartViewBox . value } ;
155
+ applyZoom ( zoomFactor , midPoint ) ;
156
+ }
157
+ } else {
158
+ event . preventDefault ( ) ;
159
+ doPan ( event ) ;
160
+ }
161
+ }
162
+
163
+ function handleTouchEnd ( event ) {
164
+ if ( event . touches . length < 2 ) {
165
+ isPinching . value = false ;
166
+ }
167
+ endPan ( ) ;
168
+ }
169
+
122
170
onMounted ( addEventListeners ) ;
123
171
onUnmounted ( removeEventListeners ) ;
124
172
@@ -132,15 +180,10 @@ export default function usePanZoom(svgRef, initialViewBox = { x: 0, y: 0, width:
132
180
svg . addEventListener ( 'mouseleave' , endPan ) ;
133
181
svg . addEventListener ( 'wheel' , zoom , { passive : false } ) ;
134
182
svg . addEventListener ( 'dblclick' , doubleClickZoom ) ;
135
- svg . addEventListener ( 'touchstart' , ( event ) => {
136
- event . preventDefault ( ) ;
137
- startPan ( event ) ;
138
- } , { passive : false } ) ;
139
- svg . addEventListener ( 'touchmove' , ( event ) => {
140
- event . preventDefault ( ) ;
141
- doPan ( event ) ;
142
- } , { passive : false } ) ;
143
- svg . addEventListener ( 'touchend' , endPan ) ;
183
+ svg . addEventListener ( 'touchstart' , handleTouchStart , { passive : false } ) ;
184
+ svg . addEventListener ( 'touchmove' , handleTouchMove , { passive : false } ) ;
185
+ svg . addEventListener ( 'touchend' , handleTouchEnd ) ;
186
+ svg . addEventListener ( 'touchcancel' , handleTouchEnd ) ;
144
187
}
145
188
146
189
function removeEventListeners ( ) {
@@ -152,9 +195,10 @@ export default function usePanZoom(svgRef, initialViewBox = { x: 0, y: 0, width:
152
195
svg . removeEventListener ( 'mouseleave' , endPan ) ;
153
196
svg . removeEventListener ( 'wheel' , zoom ) ;
154
197
svg . removeEventListener ( 'dblclick' , doubleClickZoom ) ;
155
- svg . removeEventListener ( 'touchstart' , startPan ) ;
156
- svg . removeEventListener ( 'touchmove' , doPan ) ;
157
- svg . removeEventListener ( 'touchend' , endPan ) ;
198
+ svg . removeEventListener ( 'touchstart' , handleTouchStart ) ;
199
+ svg . removeEventListener ( 'touchmove' , handleTouchMove ) ;
200
+ svg . removeEventListener ( 'touchend' , handleTouchEnd ) ;
201
+ svg . removeEventListener ( 'touchcancel' , handleTouchEnd ) ;
158
202
}
159
203
160
204
watchEffect ( ( ) => {
0 commit comments