Skip to content

Commit d1096d5

Browse files
authored
[Web] Fix focal and anchor points coordinates (#4061)
## Description Anchor and focal points were not converted to local view coordinates before being sent. This PR fixes this problem. Fixes #2929 ## Test plan Tested on example from #3124
1 parent d1368a0 commit d1096d5

5 files changed

Lines changed: 69 additions & 25 deletions

File tree

packages/react-native-gesture-handler/src/web/detectors/ScaleGestureDetector.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export default class ScaleGestureDetector implements ScaleGestureListener {
7676

7777
const div: number = pointerUp ? numOfPointers - 1 : numOfPointers;
7878

79-
const coordsSum = tracker.getAbsoluteCoordsSum();
79+
const coordsSum = tracker.getAbsoluteCoordsSum(ignoredPointer);
8080

8181
const focusX = coordsSum.x / div;
8282
const focusY = coordsSum.y / div;

packages/react-native-gesture-handler/src/web/handlers/PinchGestureHandler.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,14 @@ export default class PinchGestureHandler extends GestureHandler {
7070
}
7171

7272
protected override transformNativeEvent() {
73+
const focal = this.delegate.absoluteToLocal(
74+
this.scaleGestureDetector.focusX,
75+
this.scaleGestureDetector.focusY
76+
);
77+
7378
return {
74-
focalX: this.scaleGestureDetector.focusX,
75-
focalY: this.scaleGestureDetector.focusY,
79+
focalX: focal.x,
80+
focalY: focal.y,
7681
velocity: this.velocity,
7782
scale: this.scale,
7883
};

packages/react-native-gesture-handler/src/web/handlers/RotationGestureHandler.ts

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,24 +66,25 @@ export default class RotationGestureHandler extends GestureHandler {
6666
}
6767

6868
protected override transformNativeEvent() {
69+
const anchor = this.getAnchor();
70+
6971
return {
7072
rotation: this.rotation ? this.rotation : 0,
71-
anchorX: this.getAnchorX(),
72-
anchorY: this.getAnchorY(),
73+
anchorX: anchor.x,
74+
anchorY: anchor.y,
7375
velocity: this.velocity ? this.velocity : 0,
7476
};
7577
}
7678

77-
public getAnchorX(): number {
78-
const anchorX = this.rotationGestureDetector.anchorX;
79-
80-
return anchorX ? anchorX : this.cachedAnchorX;
81-
}
79+
private getAnchor(): { x: number; y: number } {
80+
const absX = this.rotationGestureDetector.anchorX;
81+
const absY = this.rotationGestureDetector.anchorY;
8282

83-
public getAnchorY(): number {
84-
const anchorY = this.rotationGestureDetector.anchorY;
83+
if (Number.isFinite(absX) && Number.isFinite(absY)) {
84+
return this.delegate.absoluteToLocal(absX, absY);
85+
}
8586

86-
return anchorY ? anchorY : this.cachedAnchorY;
87+
return { x: this.cachedAnchorX, y: this.cachedAnchorY };
8788
}
8889

8990
protected override onPointerDown(event: AdaptedEvent): void {
@@ -104,12 +105,9 @@ export default class RotationGestureHandler extends GestureHandler {
104105
return;
105106
}
106107

107-
if (this.getAnchorX()) {
108-
this.cachedAnchorX = this.getAnchorX();
109-
}
110-
if (this.getAnchorY()) {
111-
this.cachedAnchorY = this.getAnchorY();
112-
}
108+
const anchor = this.getAnchor();
109+
this.cachedAnchorX = anchor.x;
110+
this.cachedAnchorY = anchor.y;
113111

114112
this.tracker.track(event);
115113

@@ -123,12 +121,9 @@ export default class RotationGestureHandler extends GestureHandler {
123121
return;
124122
}
125123

126-
if (this.getAnchorX()) {
127-
this.cachedAnchorX = this.getAnchorX();
128-
}
129-
if (this.getAnchorY()) {
130-
this.cachedAnchorY = this.getAnchorY();
131-
}
124+
const anchor = this.getAnchor();
125+
this.cachedAnchorX = anchor.x;
126+
this.cachedAnchorY = anchor.y;
132127

133128
this.tracker.track(event);
134129

packages/react-native-gesture-handler/src/web/tools/GestureHandlerDelegate.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export interface GestureHandlerDelegate<TComponent, THandler> {
1313
updateDOM(): void;
1414
isPointerInBounds({ x, y }: { x: number; y: number }): boolean;
1515
measureView(): MeasureResult;
16+
absoluteToLocal(
17+
absoluteX: number,
18+
absoluteY: number
19+
): { x: number; y: number };
1620
reset(): void;
1721

1822
onBegin(): void;

packages/react-native-gesture-handler/src/web/tools/GestureHandlerWebDelegate.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,46 @@ export class GestureHandlerWebDelegate
123123
};
124124
}
125125

126+
absoluteToLocal(
127+
absoluteX: number,
128+
absoluteY: number
129+
): { x: number; y: number } {
130+
if (!this.view) {
131+
throw new Error(tagMessage('Cannot convert coords on a null view'));
132+
}
133+
134+
const rect = getEffectiveBoundingRect(this.view);
135+
const transform = getComputedStyle(this.view).transform;
136+
const matrix =
137+
transform && transform !== 'none'
138+
? new DOMMatrix(transform)
139+
: new DOMMatrix();
140+
141+
// Zero out translation — it's already reflected in the bounding rect
142+
// center, so we only need to invert the rotation+scale part.
143+
matrix.e = 0;
144+
matrix.f = 0;
145+
const inverse = matrix.inverse();
146+
147+
// Offset from the visual center of the bounding rect
148+
const rectCenterX = rect.left + rect.width / 2;
149+
const rectCenterY = rect.top + rect.height / 2;
150+
const dx = absoluteX - rectCenterX;
151+
const dy = absoluteY - rectCenterY;
152+
153+
// Apply inverse rotation+scale to get local-space offset from center
154+
const localOffset = inverse.transformPoint(new DOMPoint(dx, dy));
155+
156+
// Add back the local center (untransformed dimensions)
157+
const localCenterX = this.view.offsetWidth / 2;
158+
const localCenterY = this.view.offsetHeight / 2;
159+
160+
return {
161+
x: localCenterX + localOffset.x,
162+
y: localCenterY + localOffset.y,
163+
};
164+
}
165+
126166
reset(): void {
127167
this.eventManagers.forEach((manager: EventManager<unknown>) =>
128168
manager.resetManager()

0 commit comments

Comments
 (0)