Skip to content

Commit 5c38d14

Browse files
authored
Enhance PanAndZoom with touch support for pinch zoom and single-finger pan (#71)
* Enhance PanAndZoom with touch support for pinch zoom and single-finger pan * npm run format * Fix null handling and adjust handle_zoom signature
1 parent bcfd612 commit 5c38d14

1 file changed

Lines changed: 60 additions & 6 deletions

File tree

‎src/base/dom/pan-and-zoom.ts‎

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,59 @@ export class PanAndZoom {
4040
(e: WheelEvent) => this.#on_wheel(e),
4141
{ passive: false },
4242
);
43+
44+
let startDistance: number | null = null;
45+
let startPosition: TouchList | null = null;
46+
47+
this.target.addEventListener("touchstart", (e: TouchEvent) => {
48+
if (e.touches.length === 2) {
49+
startDistance = this.#getDistanceBetweenTouches(e.touches);
50+
} else if (e.touches.length === 1) {
51+
startPosition = e.touches;
52+
}
53+
});
54+
55+
this.target.addEventListener("touchmove", (e: TouchEvent) => {
56+
if (e.touches.length === 2) {
57+
if (startDistance !== null) {
58+
const currentDistance = this.#getDistanceBetweenTouches(
59+
e.touches,
60+
);
61+
if (Math.abs(startDistance - currentDistance) < 10) {
62+
const scale = (currentDistance / startDistance) * 4;
63+
if (startDistance < currentDistance) {
64+
this.#handle_zoom(scale * -1);
65+
} else {
66+
this.#handle_zoom(scale);
67+
}
68+
}
69+
startDistance = currentDistance;
70+
}
71+
} else if (e.touches.length === 1 && startPosition !== null) {
72+
const sx = startPosition[0]?.clientX ?? 0;
73+
const sy = startPosition[0]?.clientY ?? 0;
74+
const ex = e.touches[0]?.clientX ?? 0;
75+
const ey = e.touches[0]?.clientY ?? 0;
76+
if (Math.abs(sx - ex) < 100 && Math.abs(sy - ey) < 100) {
77+
this.#handle_pan(sx - ex, sy - ey);
78+
}
79+
startPosition = e.touches;
80+
}
81+
});
82+
83+
this.target.addEventListener("touchend", () => {
84+
startDistance = null;
85+
startPosition = null;
86+
});
87+
}
88+
89+
#getDistanceBetweenTouches(touches: TouchList) {
90+
if (touches[0] && touches[1]) {
91+
const dx = touches[0].clientX - touches[1].clientX;
92+
const dy = touches[0].clientY - touches[1].clientY;
93+
return Math.sqrt(dx * dx + dy * dy);
94+
}
95+
return 0;
4396
}
4497

4598
#on_wheel(e: WheelEvent) {
@@ -109,19 +162,20 @@ export class PanAndZoom {
109162
}
110163
}
111164

112-
#handle_zoom(delta: number, mouse: Vec2) {
113-
const mouse_world = this.camera.screen_to_world(mouse);
114-
165+
#handle_zoom(delta: number, mouse?: Vec2) {
115166
this.camera.zoom *= Math.exp(delta * -zoom_speed);
116167
this.camera.zoom = Math.min(
117168
this.max_zoom,
118169
Math.max(this.camera.zoom, this.min_zoom),
119170
);
120171

121-
const new_world = this.camera.screen_to_world(mouse);
122-
const center_delta = mouse_world.sub(new_world);
172+
if (mouse != null) {
173+
const mouse_world = this.camera.screen_to_world(mouse);
174+
const new_world = this.camera.screen_to_world(mouse);
175+
const center_delta = mouse_world.sub(new_world);
123176

124-
this.camera.translate(center_delta);
177+
this.camera.translate(center_delta);
178+
}
125179

126180
if (this.callback) {
127181
this.callback();

0 commit comments

Comments
 (0)