Make the pedigree tree zoomable
Add zoom to the pedigree: −/reset/+ buttons in the header, ctrl/⌘ + wheel, and two-finger pinch. Scaling uses the CSS `zoom` property (not transform) so the container reflows and every part stays reachable by scrolling at any level. Clamped to 40–160% and the level is remembered per device.
This commit is contained in:
+46
@@ -2464,6 +2464,49 @@
|
||||
let pedPollTimer = null;
|
||||
let pedNodes = {}; // latest ancestry map, for the progress count
|
||||
|
||||
// ---- zoom ----
|
||||
// The tree gets very wide when expanded, so it's zoomable: buttons, ctrl/⌘ +
|
||||
// wheel, and pinch. We scale via the CSS `zoom` property (not transform) so the
|
||||
// scroll container reflows and every part stays reachable. The level persists.
|
||||
const pedZoomOut = document.getElementById("ped-zoom-out");
|
||||
const pedZoomIn = document.getElementById("ped-zoom-in");
|
||||
const pedZoomReset = document.getElementById("ped-zoom-reset");
|
||||
const PED_ZOOM_MIN = 0.4, PED_ZOOM_MAX = 1.6;
|
||||
const pedZoomKey = () => `puppy-tracker:${currentUser.id}:pedigree-zoom:v1`;
|
||||
let pedZoom = 1;
|
||||
|
||||
function applyPedZoom() {
|
||||
pedZoom = Math.min(PED_ZOOM_MAX, Math.max(PED_ZOOM_MIN, Math.round(pedZoom * 100) / 100));
|
||||
pedTree.style.zoom = pedZoom;
|
||||
pedZoomReset.textContent = Math.round(pedZoom * 100) + "%";
|
||||
try { localStorage.setItem(pedZoomKey(), String(pedZoom)); } catch { /* ignore */ }
|
||||
}
|
||||
function pedZoomBy(delta) { pedZoom += delta; applyPedZoom(); }
|
||||
|
||||
pedZoomIn.addEventListener("click", () => pedZoomBy(0.2));
|
||||
pedZoomOut.addEventListener("click", () => pedZoomBy(-0.2));
|
||||
pedZoomReset.addEventListener("click", () => { pedZoom = 1; applyPedZoom(); });
|
||||
// Trackpad/desktop: ctrl or ⌘ + wheel zooms instead of scrolling the page.
|
||||
pedTree.addEventListener("wheel", (e) => {
|
||||
if (!e.ctrlKey && !e.metaKey) return;
|
||||
e.preventDefault();
|
||||
pedZoomBy(e.deltaY < 0 ? 0.1 : -0.1);
|
||||
}, { passive: false });
|
||||
// Touch: two-finger pinch.
|
||||
let pinchDist = 0, pinchZoom = 1;
|
||||
const touchDist = (t) => Math.hypot(t[0].clientX - t[1].clientX, t[0].clientY - t[1].clientY);
|
||||
pedTree.addEventListener("touchstart", (e) => {
|
||||
if (e.touches.length === 2) { pinchDist = touchDist(e.touches); pinchZoom = pedZoom; }
|
||||
}, { passive: true });
|
||||
pedTree.addEventListener("touchmove", (e) => {
|
||||
if (e.touches.length === 2 && pinchDist > 0) {
|
||||
e.preventDefault();
|
||||
pedZoom = pinchZoom * (touchDist(e.touches) / pinchDist);
|
||||
applyPedZoom();
|
||||
}
|
||||
}, { passive: false });
|
||||
pedTree.addEventListener("touchend", (e) => { if (e.touches.length < 2) pinchDist = 0; });
|
||||
|
||||
// The looked-up tree is cached locally per dog id, so reopening the page paints
|
||||
// instantly and still shows the last-known tree offline. The server caches it
|
||||
// too (per dog, permanently); this is just the client-side mirror.
|
||||
@@ -2484,6 +2527,9 @@
|
||||
const id = loadConfig().pedigreeId;
|
||||
appEl.hidden = true;
|
||||
pedScreen.hidden = false;
|
||||
const saved = parseFloat(localStorage.getItem(pedZoomKey()));
|
||||
pedZoom = Number.isFinite(saved) ? saved : 1;
|
||||
applyPedZoom();
|
||||
if (!id) { setPedStatus("Set your dog's SKK id in Settings to see its pedigree.", ""); return; }
|
||||
lookupPedigree(id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user