diff --git a/src/core/instance.core.ts b/src/core/instance.core.ts index a6df3dd9..022cd2cc 100644 --- a/src/core/instance.core.ts +++ b/src/core/instance.core.ts @@ -54,6 +54,9 @@ export class ZoomPanPinch { public mounted = true; + public pinchLastCenterX: number | null = null; + public pinchLastCenterY: number | null = null; + public transformState: ReactZoomPanPinchState; public setup: LibrarySetup; public observer?: ResizeObserver; @@ -180,7 +183,9 @@ export class ZoomPanPinch { const currentHeight = contentComponent.offsetHeight; if (currentWidth > 0 || currentHeight > 0) { - this.onInitCallbacks.forEach((callback) => callback(getContext(this))); + this.onInitCallbacks.forEach((callback) => + callback(getContext(this)), + ); this.setCenter(); this.observer?.disconnect(); diff --git a/src/core/pinch/pinch.logic.ts b/src/core/pinch/pinch.logic.ts index 6e88bc77..4390b4d4 100644 --- a/src/core/pinch/pinch.logic.ts +++ b/src/core/pinch/pinch.logic.ts @@ -7,8 +7,28 @@ import { calculateTouchMidPoint, getTouchDistance, } from "./pinch.utils"; -import { handleCalculateBounds } from "../bounds/bounds.utils"; +import { + getMouseBoundedPosition, + handleCalculateBounds, +} from "../bounds/bounds.utils"; import { handleCalculateZoomPositions } from "../zoom/zoom.utils"; +import { getPaddingValue } from "../pan/panning.utils"; + +const getTouchCenter = (event: TouchEvent) => { + let totalX = 0; + let totalY = 0; + // Sum up the positions of all touches + for (let i = 0; i < 2; i++) { + totalX += event.touches[i].clientX; + totalY += event.touches[i].clientY; + } + + // Calculate the average position + const x = totalX / 2; + const y = totalY / 2; + + return { x, y }; +}; export const handlePinchStart = ( contextInstance: ReactZoomPanPinchContext, @@ -21,6 +41,10 @@ export const handlePinchStart = ( contextInstance.pinchStartScale = contextInstance.transformState.scale; contextInstance.isPanning = false; + const center = getTouchCenter(event); + contextInstance.pinchLastCenterX = center.x; + contextInstance.pinchLastCenterY = center.y; + handleCancelAnimation(contextInstance); }; @@ -28,9 +52,10 @@ export const handlePinchZoom = ( contextInstance: ReactZoomPanPinchContext, event: TouchEvent, ): void => { - const { contentComponent, pinchStartDistance } = contextInstance; + const { contentComponent, pinchStartDistance, wrapperComponent } = + contextInstance; const { scale } = contextInstance.transformState; - const { limitToBounds, centerZoomedOut, zoomAnimation } = + const { limitToBounds, centerZoomedOut, zoomAnimation, alignmentAnimation } = contextInstance.setup; const { disabled, size } = zoomAnimation; @@ -45,7 +70,15 @@ export const handlePinchZoom = ( const currentDistance = getTouchDistance(event); const newScale = calculatePinchZoom(contextInstance, currentDistance); - if (newScale === scale) return; + const center = getTouchCenter(event); + // pan should be scale invariant. + const panX = center.x - (contextInstance.pinchLastCenterX || 0); + const panY = center.y - (contextInstance.pinchLastCenterY || 0); + + if (newScale === scale && 0 == panX && 0 == panY) return; + + contextInstance.pinchLastCenterX = center.x; + contextInstance.pinchLastCenterY = center.y; const bounds = handleCalculateBounds(contextInstance, newScale); @@ -64,7 +97,23 @@ export const handlePinchZoom = ( contextInstance.pinchMidpoint = midPoint; contextInstance.lastDistance = currentDistance; - contextInstance.setTransformState(newScale, x, y); + const { sizeX, sizeY } = alignmentAnimation; + const paddingValueX = getPaddingValue(contextInstance, sizeX); + const paddingValueY = getPaddingValue(contextInstance, sizeY); + + const newPositionX = x + panX; + const newPositionY = y + panY; + const { x: finalX, y: finalY } = getMouseBoundedPosition( + newPositionX, + newPositionY, + bounds, + limitToBounds, + paddingValueX, + paddingValueY, + wrapperComponent, + ); + + contextInstance.setTransformState(newScale, finalX, finalY); }; export const handlePinchStop = (