Fix: Rework wheel and touch handling for pan and zoom (#1956)

## Description:

Fixes https://github.com/openfrontio/OpenFrontIO/issues/1827.

Summary:
- Restore the code of `onScroll()` method which was modified by #1717.
- Rework he `wheel` event logic to better distinguish between trackpad
pans and mouse wheel zooms. It now uses a heuristic where any scroll
event with a horizontal component (`deltaX !== 0`) is treated as a pan,
while purely vertical scrolls are treated as a zoom. This is a
compromise that fixes mouse wheel behavior, with the trade-off that
vertical-only trackpad swipes now also zoom (which is difficult for
human fingers to trigger).
- Solve the screen jittering problem when touching the screen by 2
fingers (which because when the second finger touches, `lastPointerX`
and `lastPointerY` are not recalculated in time.).

**Screen recording before fixing:**

(macbook, broken scroll zoom)


https://github.com/user-attachments/assets/5ba0fc24-2aec-4ecb-ab0f-2b0a0574d57e

(iphone, 2-fingers drag works well, but screen jittering exists)


https://github.com/user-attachments/assets/374f4f0f-688c-4b75-a20a-177144556c8c

**and after fixing:**

(macbook, scroll works well)


https://github.com/user-attachments/assets/b7e3447f-9936-4971-90c4-8644d0a9619d

(iphone, 2-fingers drag works well, no screen jittering)


https://github.com/user-attachments/assets/9d952082-a672-42b6-a117-7a9fed6ea5f0



## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

yumika8269
This commit is contained in:
Moki 💤
2025-08-29 07:17:03 +09:00
committed by evanpelle
parent c874df4d04
commit a4aa014e5e
+23 -36
View File
@@ -173,9 +173,10 @@ export class InputHandler {
this.canvas.addEventListener(
"wheel",
(e) => {
this.onScroll(e);
if (!this.onTrackpadPan(e)) {
this.onScroll(e);
}
this.onShiftScroll(e);
this.onTrackpadPan(e);
e.preventDefault();
},
{ passive: false },
@@ -413,20 +414,8 @@ export class InputHandler {
const realCtrl =
this.activeKeys.has("ControlLeft") ||
this.activeKeys.has("ControlRight");
const isZoomGesture =
event.ctrlKey ||
event.metaKey ||
Math.abs(event.deltaZ) > 0 ||
(event.deltaMode === 1 && Math.abs(event.deltaY) > 0) ||
(event.deltaMode === 0 && Math.abs(event.deltaY) >= 50);
if (isZoomGesture) {
const ratio = event.ctrlKey && !realCtrl ? 10 : 1;
this.eventBus.emit(
new ZoomEvent(event.x, event.y, event.deltaY * ratio),
);
}
const ratio = event.ctrlKey && !realCtrl ? 10 : 1; // Compensate pinch-zoom low sensitivity
this.eventBus.emit(new ZoomEvent(event.x, event.y, event.deltaY * ratio));
}
}
@@ -438,32 +427,25 @@ export class InputHandler {
}
}
private onTrackpadPan(event: WheelEvent) {
if (event.shiftKey) {
return;
private onTrackpadPan(event: WheelEvent): boolean {
if (event.shiftKey || event.ctrlKey || event.metaKey) {
return false;
}
if (event.ctrlKey || event.metaKey) {
return;
const isTrackpadPan = event.deltaMode === 0 && event.deltaX !== 0;
if (!isTrackpadPan) {
return false;
}
const isTrackpadPan =
event.deltaMode === 0 &&
(Math.abs(event.deltaX) > 0 || Math.abs(event.deltaY) > 0) &&
((Math.abs(event.deltaX) > 0 && Math.abs(event.deltaY) > 0) ||
event.deltaX % 1 !== 0 ||
event.deltaY % 1 !== 0 ||
(Math.abs(event.deltaX) < 30 && Math.abs(event.deltaY) < 30));
const panSensitivity = 1.0;
const deltaX = -event.deltaX * panSensitivity;
const deltaY = -event.deltaY * panSensitivity;
if (isTrackpadPan) {
const panSensitivity = 1.0;
const deltaX = -event.deltaX * panSensitivity;
const deltaY = -event.deltaY * panSensitivity;
if (Math.abs(deltaX) > 0.5 || Math.abs(deltaY) > 0.5) {
this.eventBus.emit(new DragEvent(deltaX, deltaY));
}
if (Math.abs(deltaX) > 0.5 || Math.abs(deltaY) > 0.5) {
this.eventBus.emit(new DragEvent(deltaX, deltaY));
}
return true;
}
private onPointerMove(event: PointerEvent) {
@@ -513,6 +495,11 @@ export class InputHandler {
private onTouchStart(event: TouchEvent) {
if (event.touches.length === 2) {
event.preventDefault();
// Solve screen jittering problem
const touch1 = event.touches[0];
const touch2 = event.touches[1];
this.lastPointerX = (touch1.clientX + touch2.clientX) / 2;
this.lastPointerY = (touch1.clientY + touch2.clientY) / 2;
}
}