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:
parent
d990627200
commit
8f700e7f66
18
harnesses/gtauri/frontend/dist/index.html
vendored
18
harnesses/gtauri/frontend/dist/index.html
vendored
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user