diff options
| author | tv <tv@krebsco.de> | 2026-10-31 06:19:54 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-31 06:19:54 +0100 |
| commit | c60f8e81cab9a5a43c8eb484a458af13a53517d7 (patch) | |
| tree | 0c24a0ecb7bf27f1143a6a2562be9a21aef22172 /src/api/dashboard/dashboard.html | |
| parent | 4a587f665b2eddb8cda4dce517ecf8a8e22c6cad (diff) | |
dashboard: add pinch zoom
Diffstat (limited to 'src/api/dashboard/dashboard.html')
| -rw-r--r-- | src/api/dashboard/dashboard.html | 52 |
1 files changed, 48 insertions, 4 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index 5ca7409..38a1a5c 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -152,7 +152,7 @@ </div> <div class="legend" id="legend"></div> <div class="legend"> - <span class="hint">drag to pan, wheel to zoom, double-click to reset; drag a node to + <span class="hint">drag to pan, wheel or pinch to zoom, double-click to reset; drag a node to pin it, double-click it to let go</span> </div> </div> @@ -1028,11 +1028,13 @@ function pointer(event) { const box = graph.getBoundingClientRect(); return { x: event.clientX - box.left, y: event.clientY - box.top }; } +// How far it zooms: from a quarter to eight times. +const zoomed = k => Math.min(8, Math.max(.25, k)); // The wheel zooms around the pointer. graph.addEventListener("wheel", event => { event.preventDefault(); const at = pointer(event); - const k = Math.min(8, Math.max(.25, view.k * Math.exp(-event.deltaY * .002))); + const k = zoomed(view.k * Math.exp(-event.deltaY * .002)); view.x = at.x - (at.x - view.x) * k / view.k; view.y = at.y - (at.y - view.y) * k / view.k; view.k = k; @@ -1045,7 +1047,47 @@ function pressing(event, move, click) { press = { from: pointer(event), moved: false, move, click }; graph.setPointerCapture(event.pointerId); } +// Fingers on the map, by pointer. Two pinch: the map zooms around the point between +// them and moves with it. A press (`pressing`) is given up when the second comes. +const touching = new Map(); +let pinch = null; +const between = (a, b) => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }); +// From the first two fingers as they are now. +function pinchFrom() { + const [a, b] = touching.values(); + pinch = { view: { ...view }, at: between(a, b), + distance: Math.max(Math.hypot(b.x - a.x, b.y - a.y), 1) }; +} +function pinched() { + const [a, b] = touching.values(); + const at = between(a, b); + const k = zoomed(pinch.view.k * Math.hypot(b.x - a.x, b.y - a.y) / pinch.distance); + // What was between the fingers stays between them. + view.x = at.x - (pinch.at.x - pinch.view.x) * k / pinch.view.k; + view.y = at.y - (pinch.at.y - pinch.view.y) * k / pinch.view.k; + view.k = k; + moveView(); +} +// Before the map's own handlers (a node's included): a second finger takes over. +graph.addEventListener("pointerdown", event => { + if (event.pointerType !== "touch") return; + touching.set(event.pointerId, pointer(event)); + graph.setPointerCapture(event.pointerId); + if (touching.size < 2) return; + press = null; + graph.classList.remove("dragging"); + pinchFrom(); + event.stopImmediatePropagation(); +}, true); +// A finger lifted: the others pinch on from where they are, or (one left) do nothing +// until lifted too. +function untouched(event) { + if (!touching.delete(event.pointerId)) return; + if (touching.size >= 2) pinchFrom(); else pinch = null; +} graph.addEventListener("pointermove", event => { + if (touching.has(event.pointerId)) touching.set(event.pointerId, pointer(event)); + if (pinch) { pinched(); return; } if (!press) return; const at = pointer(event); const dx = at.x - press.from.x, dy = at.y - press.from.y; @@ -1054,13 +1096,15 @@ graph.addEventListener("pointermove", event => { graph.classList.add("dragging"); press.move(dx, dy); }); -graph.addEventListener("pointerup", () => { +graph.addEventListener("pointerup", event => { + untouched(event); const click = press && !press.moved ? press.click : null; press = null; graph.classList.remove("dragging"); if (click) click(); }); -graph.addEventListener("pointercancel", () => { +graph.addEventListener("pointercancel", event => { + untouched(event); press = null; graph.classList.remove("dragging"); }); |
