gtauri: clamp viewer pan/zoom to image edges

Pan is now limited to the image bounds (|offset| <= (rendered -
viewport)/2 per axis), so the image can never be dragged off-screen
leaving blank space around it; on a letterboxed axis panning is
locked to centered. Pinch can no longer zoom out past fit. Clamping
lives in vApply() so pan, pinch and reset all share it; still
compositor-only (transform only).
This commit is contained in:
Greg Pomerantz 2026-11-01 17:44:14 -05:00
parent d990627200
commit 8f700e7f66

View File

@ -269,7 +269,21 @@ requestAnimationFrame(frTick);
let vScale = 1, vTx = 0, vTy = 0; let vScale = 1, vTx = 0, vTy = 0;
const base = { w: 0, h: 0 }; const base = { w: 0, h: 0 };
function fitScale() {
return Math.min(innerWidth / base.w, innerHeight / base.h);
}
function vApply() { function vApply() {
// Clamp: never zoom out past fit, and pan is limited to the image
// edges — the image can never be dragged off-screen leaving blank
// space around it. On a letterboxed axis (image smaller than the
// viewport in that dimension) panning is locked to centered.
const s = fitScale();
if (vScale < s) vScale = s;
const mx = Math.max(0, (base.w * vScale - innerWidth) / 2);
const my = Math.max(0, (base.h * vScale - innerHeight) / 2);
if (vTx > mx) vTx = mx; else if (vTx < -mx) vTx = -mx;
if (vTy > my) vTy = my; else if (vTy < -my) vTy = -my;
vimg.style.transform = vimg.style.transform =
"translate(" + (vTx - base.w * vScale / 2) + "px," + (vTy - base.h * vScale / 2) + "px) scale(" + vScale + ")"; "translate(" + (vTx - base.w * vScale / 2) + "px," + (vTy - base.h * vScale / 2) + "px) scale(" + vScale + ")";
} }
@ -370,7 +384,7 @@ viewer.addEventListener("pointermove", (e) => {
p.x = e.clientX; p.y = e.clientY; p.x = e.clientX; p.y = e.clientY;
const [a, b] = [...pts.values()]; const [a, b] = [...pts.values()];
const d = Math.hypot(a.x - b.x, a.y - b.y); const d = Math.hypot(a.x - b.x, a.y - b.y);
if (pinchD0 > 0) vScale = Math.max(0.05, pinchS0 * d / pinchD0); if (pinchD0 > 0) vScale = Math.max(fitScale(), pinchS0 * d / pinchD0);
vApply(); vApply();
} }
}); });
@ -378,7 +392,7 @@ const endPt = (e) => { pts.delete(e.pointerId); };
viewer.addEventListener("pointerup", endPt); viewer.addEventListener("pointerup", endPt);
viewer.addEventListener("pointercancel", endPt); viewer.addEventListener("pointercancel", endPt);
viewer.addEventListener("dblclick", () => { viewer.addEventListener("dblclick", () => {
vScale = Math.min(innerWidth / base.w, innerHeight / base.h); vScale = fitScale();
vTx = 0; vTy = 0; vApply(); vTx = 0; vTy = 0; vApply();
}); });
</script> </script>