@@ -305,13 +305,14 @@ export default function GraphFunctionalPage() {
305305 let pinchActive = false ;
306306 let lastX = 0 , lastY = 0 ;
307307 let lastDist = 0 ;
308- let lastMid = { x : 0 , y : 0 } ;
309- let startView = { x : 0 , y : 0 , k : 1 } ;
310308
311- const getTouchMid = ( t1 : Touch , t2 : Touch ) => ( {
312- x : ( t1 . clientX + t2 . clientX ) / 2 ,
313- y : ( t1 . clientY + t2 . clientY ) / 2 ,
314- } ) ;
309+ const getTouchMid = ( t1 : Touch , t2 : Touch ) => {
310+ const rect = container . getBoundingClientRect ( ) ;
311+ return {
312+ x : ( t1 . clientX + t2 . clientX ) / 2 - rect . left ,
313+ y : ( t1 . clientY + t2 . clientY ) / 2 - rect . top ,
314+ } ;
315+ } ;
315316 const getTouchDist = ( t1 : Touch , t2 : Touch ) => {
316317 const dx = t1 . clientX - t2 . clientX ;
317318 const dy = t1 . clientY - t2 . clientY ;
@@ -327,25 +328,28 @@ export default function GraphFunctionalPage() {
327328 pinchActive = true ;
328329 panActive = false ;
329330 lastDist = getTouchDist ( e . touches [ 0 ] , e . touches [ 1 ] ) ;
330- lastMid = getTouchMid ( e . touches [ 0 ] , e . touches [ 1 ] ) ;
331- startView = { ...viewRef . current } ;
332331 }
333332 } ;
334333 const onTouchMove = ( e : TouchEvent ) => {
335334 if ( pinchActive && e . touches . length === 2 ) {
336335 e . preventDefault ( ) ;
337336 const dist = getTouchDist ( e . touches [ 0 ] , e . touches [ 1 ] ) ;
338- const mid = getTouchMid ( e . touches [ 0 ] , e . touches [ 1 ] ) ;
339- const scale = dist / lastDist ;
340- const newK = Math . max ( 0.2 , Math . min ( 4 , startView . k * scale ) ) ;
341- // Pan so that the midpoint stays under the same screen point
342- const dx = ( mid . x - lastMid . x ) / newK ;
343- const dy = ( mid . y - lastMid . y ) / newK ;
344- setView ( {
345- x : startView . x + dx ,
346- y : startView . y + dy ,
347- k : newK ,
348- } ) ;
337+ if ( lastDist === 0 ) {
338+ lastDist = dist ;
339+ return ;
340+ }
341+ const mid = getTouchMid ( e . touches [ 0 ] , e . touches [ 1 ] ) ; // container-relative
342+ const { x, y, k } = viewRef . current ;
343+ const scaleDelta = dist / lastDist ;
344+ const unclampedK = k * scaleDelta ;
345+ const newK = Math . max ( 0.3 , Math . min ( 3.0 , unclampedK ) ) ;
346+ // Keep the world point under the current midpoint stationary while scaling
347+ const worldX = ( mid . x - x ) / k ;
348+ const worldY = ( mid . y - y ) / k ;
349+ const newX = mid . x - worldX * newK ;
350+ const newY = mid . y - worldY * newK ;
351+ setView ( { x : newX , y : newY , k : newK } ) ;
352+ lastDist = dist ;
349353 } else if ( panActive && e . touches . length === 1 ) {
350354 const dx = e . touches [ 0 ] . clientX - lastX ;
351355 const dy = e . touches [ 0 ] . clientY - lastY ;
0 commit comments