improve pinch to zoom

This commit is contained in:
marlon-erler
2026-09-24 15:55:43 +02:00
parent f3896c497b
commit 29ac50e305
4 changed files with 60 additions and 81 deletions
+44 -67
View File
@@ -230,31 +230,30 @@ export function localeCompare(a: string, b: string): number {
export function implementPinchZoom(canvas: HTMLElement) { export function implementPinchZoom(canvas: HTMLElement) {
let pinching = false; let pinching = false;
let dragging = false; let dragging = false;
let scrolling = false;
let lastElement: HTMLElement | undefined = undefined; let lastElement: HTMLElement | undefined = undefined;
let initialDistance: number, let initialDistance: number,
currentZoom: number, currentZoom: number,
initialZoom: number, initialZoom: number,
currentX: number, currentX: number,
currentY: number, currentY: number,
initialX: number, initialX: number,
initialY: number, initialY: number,
initialTouchX: number, initialTouchX: number,
initialTouchY: number; initialTouchY: number;
function reset() { function reset() {
initialDistance = 0; initialDistance = 0;
currentZoom = 1; currentZoom = 1;
initialZoom = 1; initialZoom = 1;
currentX = 0; currentX = 0;
currentY = 0; currentY = 0;
initialX = 0; initialX = 0;
initialY = 0; initialY = 0;
initialTouchX = 0; initialTouchX = 0;
initialTouchY = 0; initialTouchY = 0;
} }
reset(); reset();
@@ -264,60 +263,44 @@ export function implementPinchZoom(canvas: HTMLElement) {
const element = (): HTMLElement | null => canvas.querySelector(".zoom"); const element = (): HTMLElement | null => canvas.querySelector(".zoom");
const distance = (e: TouchEvent) => const distance = (e: TouchEvent) =>
Math.hypot( Math.hypot(
e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageX - e.touches[1].pageX,
e.touches[0].pageY - e.touches[1].pageY, e.touches[0].pageY - e.touches[1].pageY,
); );
const point = (e: TouchEvent, direction: "x" | "y", i: number) => const point = (e: TouchEvent, direction: "x" | "y", i: number) =>
e.touches[i][direction == "x" ? "clientX" : "clientY"]; e.touches[i][direction == "x" ? "clientX" : "clientY"];
const midpoint = (e: TouchEvent, direction: "x" | "y") => const midpoint = (e: TouchEvent, direction: "x" | "y") =>
(point(e, direction, 0) + point(e, direction, 1)) / 2; (point(e, direction, 0) + point(e, direction, 1)) / 2;
function apply(factor: number, _offset?: [number, number]) { function apply(factor: number, _offset?: [number, number]) {
const el = element(); const el = element();
if (!el) return; if (!el) return;
if (factor < MIN) return apply(MIN, _offset); if (factor < MIN) return apply(MIN, _offset);
if (factor > MAX) return apply(5, _offset); if (factor > MAX) return apply(5, _offset);
let x: number, y: number; let x: number, y: number;
if (_offset) [x, y] = _offset; if (_offset) [x, y] = _offset;
else { else {
x = currentX; x = currentX;
y = currentY; y = currentY;
} }
el.style.transform = `scale(${factor.toString()}) translate(${x}px, ${y}px)`; el.style.transform = `scale(${factor.toString()}) translate(${x}px, ${y}px)`;
currentX = x; currentX = x;
currentY = y; currentY = y;
currentZoom = factor; currentZoom = factor;
} }
canvas.addEventListener("wheel", (event: WheelEvent) => { canvas.addEventListener("wheel", (event: WheelEvent) => {
event.preventDefault(); event.preventDefault();
if (!scrolling) { initialZoom = currentZoom;
initialZoom = currentZoom; initialX = currentX;
initialX = currentX; initialY = currentY;
initialY = currentY;
scrolling = true
}
const canvasWidth = canvas.offsetWidth; apply(currentZoom - event.deltaY * 0.005);
const canvasHeight = canvas.offsetHeight;
const initialWidth = canvasWidth * initialZoom;
const initialHeight = canvasHeight * initialZoom;
const currentWidth = canvasWidth * currentZoom;
const currentHeight = canvasHeight * currentZoom;
const differenceWidth = currentWidth - initialWidth;
const differenceHeight = currentHeight - initialHeight;
apply(currentZoom - event.deltaY * 0.005, [initialX - differenceWidth, initialY - differenceHeight]);
}); });
canvas.addEventListener("scrollend", () => {
scrolling = false;
})
canvas.addEventListener("scroll", (event: Event) => { canvas.addEventListener("scroll", (event: Event) => {
event.preventDefault(); event.preventDefault();
}); });
canvas.addEventListener("mousedown", (event: MouseEvent) => { canvas.addEventListener("mousedown", (event: MouseEvent) => {
@@ -378,14 +361,8 @@ export function implementPinchZoom(canvas: HTMLElement) {
if (event.touches.length < 2) return; if (event.touches.length < 2) return;
event.preventDefault(); event.preventDefault();
const currentDistance = distance(event); const currentDistance = distance(event);
const midX = midpoint(event, "x");
const midY = midpoint(event, "y");
const ratio = currentDistance / initialDistance; const ratio = currentDistance / initialDistance;
const difference = currentDistance - initialDistance; apply(initialZoom * ratio);
apply(initialZoom * ratio, [
initialX + (midX - difference - initialTouchX) / initialZoom,
initialY + (midY - difference - initialTouchY) / initialZoom,
]);
}); });
} }
-13
View File
@@ -25,19 +25,6 @@
padding: var(--content-padding); padding: var(--content-padding);
} }
#chat-page > div > #main > #task-page .zoom {
display: flex;
align-items: start;
gap: var(--large-gap);
width: auto;
height: auto;
transform-origin: top left;
padding: var(--content-padding);
}
#board-content .main-content { #board-content .main-content {
overflow: auto; overflow: auto;
} }
+1 -1
View File
@@ -9,7 +9,7 @@ import ConnectionModel from "../../Model/Global/connectionModel";
import ChatListModel from "../../Model/Chat/chatListModel"; import ChatListModel from "../../Model/Chat/chatListModel";
export default class CoreViewModel { export default class CoreViewModel {
readonly BUILD = "Build 26.09.24.B"; readonly BUILD = "Build 26.09.24.C";
translations: Translations; translations: Translations;
+15
View File
@@ -97,3 +97,18 @@ body[theme="dark"] #background-wrapper #grass-2 {
--color-1: hsl(110, 60%, 30%); --color-1: hsl(110, 60%, 30%);
--color-2: hsl(110, 50%, 24%); --color-2: hsl(110, 50%, 24%);
} }
/* Zoom */
.zoom {
display: flex;
align-items: start;
gap: var(--large-gap);
width: auto;
height: auto;
transform-origin: center;
padding: var(--content-padding);
}