diff --git a/packages/react-native-gesture-handler/src/web/detectors/ScaleGestureDetector.ts b/packages/react-native-gesture-handler/src/web/detectors/ScaleGestureDetector.ts index 9392c133f9..b214213ee6 100644 --- a/packages/react-native-gesture-handler/src/web/detectors/ScaleGestureDetector.ts +++ b/packages/react-native-gesture-handler/src/web/detectors/ScaleGestureDetector.ts @@ -76,7 +76,7 @@ export default class ScaleGestureDetector implements ScaleGestureListener { const div: number = pointerUp ? numOfPointers - 1 : numOfPointers; - const coordsSum = tracker.getAbsoluteCoordsSum(); + const coordsSum = tracker.getAbsoluteCoordsSum(ignoredPointer); const focusX = coordsSum.x / div; const focusY = coordsSum.y / div; diff --git a/packages/react-native-gesture-handler/src/web/handlers/PinchGestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/PinchGestureHandler.ts index 9c9bf1f2c0..42462576d9 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/PinchGestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/PinchGestureHandler.ts @@ -70,9 +70,14 @@ export default class PinchGestureHandler extends GestureHandler { } protected override transformNativeEvent() { + const focal = this.delegate.absoluteToLocal( + this.scaleGestureDetector.focusX, + this.scaleGestureDetector.focusY + ); + return { - focalX: this.scaleGestureDetector.focusX, - focalY: this.scaleGestureDetector.focusY, + focalX: focal.x, + focalY: focal.y, velocity: this.velocity, scale: this.scale, }; diff --git a/packages/react-native-gesture-handler/src/web/handlers/RotationGestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/RotationGestureHandler.ts index 6dd8b4dd47..3c589cf425 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/RotationGestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/RotationGestureHandler.ts @@ -66,24 +66,25 @@ export default class RotationGestureHandler extends GestureHandler { } protected override transformNativeEvent() { + const anchor = this.getAnchor(); + return { rotation: this.rotation ? this.rotation : 0, - anchorX: this.getAnchorX(), - anchorY: this.getAnchorY(), + anchorX: anchor.x, + anchorY: anchor.y, velocity: this.velocity ? this.velocity : 0, }; } - public getAnchorX(): number { - const anchorX = this.rotationGestureDetector.anchorX; - - return anchorX ? anchorX : this.cachedAnchorX; - } + private getAnchor(): { x: number; y: number } { + const absX = this.rotationGestureDetector.anchorX; + const absY = this.rotationGestureDetector.anchorY; - public getAnchorY(): number { - const anchorY = this.rotationGestureDetector.anchorY; + if (Number.isFinite(absX) && Number.isFinite(absY)) { + return this.delegate.absoluteToLocal(absX, absY); + } - return anchorY ? anchorY : this.cachedAnchorY; + return { x: this.cachedAnchorX, y: this.cachedAnchorY }; } protected override onPointerDown(event: AdaptedEvent): void { @@ -104,12 +105,9 @@ export default class RotationGestureHandler extends GestureHandler { return; } - if (this.getAnchorX()) { - this.cachedAnchorX = this.getAnchorX(); - } - if (this.getAnchorY()) { - this.cachedAnchorY = this.getAnchorY(); - } + const anchor = this.getAnchor(); + this.cachedAnchorX = anchor.x; + this.cachedAnchorY = anchor.y; this.tracker.track(event); @@ -123,12 +121,9 @@ export default class RotationGestureHandler extends GestureHandler { return; } - if (this.getAnchorX()) { - this.cachedAnchorX = this.getAnchorX(); - } - if (this.getAnchorY()) { - this.cachedAnchorY = this.getAnchorY(); - } + const anchor = this.getAnchor(); + this.cachedAnchorX = anchor.x; + this.cachedAnchorY = anchor.y; this.tracker.track(event); diff --git a/packages/react-native-gesture-handler/src/web/tools/GestureHandlerDelegate.ts b/packages/react-native-gesture-handler/src/web/tools/GestureHandlerDelegate.ts index 087bf04300..d9edf3f634 100644 --- a/packages/react-native-gesture-handler/src/web/tools/GestureHandlerDelegate.ts +++ b/packages/react-native-gesture-handler/src/web/tools/GestureHandlerDelegate.ts @@ -13,6 +13,10 @@ export interface GestureHandlerDelegate { updateDOM(): void; isPointerInBounds({ x, y }: { x: number; y: number }): boolean; measureView(): MeasureResult; + absoluteToLocal( + absoluteX: number, + absoluteY: number + ): { x: number; y: number }; reset(): void; onBegin(): void; diff --git a/packages/react-native-gesture-handler/src/web/tools/GestureHandlerWebDelegate.ts b/packages/react-native-gesture-handler/src/web/tools/GestureHandlerWebDelegate.ts index 39d6cf802b..8bc82f2b2a 100644 --- a/packages/react-native-gesture-handler/src/web/tools/GestureHandlerWebDelegate.ts +++ b/packages/react-native-gesture-handler/src/web/tools/GestureHandlerWebDelegate.ts @@ -123,6 +123,46 @@ export class GestureHandlerWebDelegate }; } + absoluteToLocal( + absoluteX: number, + absoluteY: number + ): { x: number; y: number } { + if (!this.view) { + throw new Error(tagMessage('Cannot convert coords on a null view')); + } + + const rect = getEffectiveBoundingRect(this.view); + const transform = getComputedStyle(this.view).transform; + const matrix = + transform && transform !== 'none' + ? new DOMMatrix(transform) + : new DOMMatrix(); + + // Zero out translation — it's already reflected in the bounding rect + // center, so we only need to invert the rotation+scale part. + matrix.e = 0; + matrix.f = 0; + const inverse = matrix.inverse(); + + // Offset from the visual center of the bounding rect + const rectCenterX = rect.left + rect.width / 2; + const rectCenterY = rect.top + rect.height / 2; + const dx = absoluteX - rectCenterX; + const dy = absoluteY - rectCenterY; + + // Apply inverse rotation+scale to get local-space offset from center + const localOffset = inverse.transformPoint(new DOMPoint(dx, dy)); + + // Add back the local center (untransformed dimensions) + const localCenterX = this.view.offsetWidth / 2; + const localCenterY = this.view.offsetHeight / 2; + + return { + x: localCenterX + localOffset.x, + y: localCenterY + localOffset.y, + }; + } + reset(): void { this.eventManagers.forEach((manager: EventManager) => manager.resetManager()