Widen the edge-tab tap target, split scroll from reposition #9

Merged
sulthan merged 5 commits from userscript-edge-tab-hitbox into main 2026-07-28 20:36:26 +07:00
Showing only changes of commit cbb8d67276 - Show all commits
+60 -6
View File
@@ -904,6 +904,7 @@
const FAB_KEY = "mangabm:fabpos";
const FAB_MARGIN = 12; // vertical breathing room at the top and bottom
const FAB_EDGE = 0; // horizontal: an edge tab sits flush against the side
const ARM_MS = 400; // hold this long on the invisible strip to arm a drag
function clamp(v, lo, hi) {
return Math.max(lo, Math.min(hi, v));
@@ -947,23 +948,69 @@
applyFabPos(fab, side, y);
}
// A gesture that starts on the visible sliver drags immediately, as it always
// has. A gesture that starts on the invisible #hit strip scrolls the page
// instead — unless it is held for ARM_MS first, which arms a reposition drag.
// ponytail: the scroll is hand-rolled (no momentum/fling) and assumes the
// document is the scroller. That is the price of touch-action:none, which we
// need so a hold can claim pixels a swipe would otherwise scroll. If the
// stiffness annoys, give #hit touch-action:pan-y and accept that vertical
// repositioning must start on the 7px sliver.
function makeDraggable(fab) {
let down = false, moved = false, sx = 0, sy = 0, offX = 0, offY = 0;
let down = false, moved = false, armed = false, scrolling = false, fromHit = false;
let sx = 0, sy = 0, offX = 0, offY = 0, lastY = 0, armTimer = 0;
function reset() {
down = moved = armed = scrolling = fromHit = false;
clearTimeout(armTimer);
fab.classList.remove("armed");
fab.style.transition = "";
}
function scrollPage(e) {
e.preventDefault();
window.scrollBy(0, lastY - e.clientY);
lastY = e.clientY;
}
fab.addEventListener("pointerdown", (e) => {
delete fab.dataset.dragged; // stale flag would swallow this gesture's click
down = true;
moved = false;
armed = false;
scrolling = false;
fromHit = e.target.id === "hit";
sx = e.clientX;
sy = e.clientY;
lastY = e.clientY;
const r = fab.getBoundingClientRect();
offX = e.clientX - r.left;
offY = e.clientY - r.top;
fab.setPointerCapture(e.pointerId);
fab.style.transition = "none";
clearTimeout(armTimer);
armTimer = setTimeout(() => {
armed = true;
fab.classList.add("armed"); // visible confirmation the hold took
}, ARM_MS);
});
fab.addEventListener("pointermove", (e) => {
if (!down) return;
if (!moved && (Math.abs(e.clientX - sx) > 6 || Math.abs(e.clientY - sy) > 6)) moved = true;
if (!moved) return;
if (scrolling) {
scrollPage(e);
return;
}
if (!moved) {
if (Math.abs(e.clientX - sx) <= 6 && Math.abs(e.clientY - sy) <= 6) return;
clearTimeout(armTimer);
if (fromHit && !armed) {
scrolling = true;
scrollPage(e);
return;
}
moved = true;
}
e.preventDefault();
const w = fab.offsetWidth, h = fab.offsetHeight;
fab.style.left = clamp(e.clientX - offX, 0, window.innerWidth - w) + "px";
@@ -971,11 +1018,16 @@
fab.style.top = clamp(e.clientY - offY, 0, window.innerHeight - h) + "px";
fab.style.bottom = "auto";
});
fab.addEventListener("pointerup", (e) => {
if (!down) return;
down = false;
fab.style.transition = "";
if (!moved) return; // tap — let the click handler toggle the panel
const didDrag = moved, didScroll = scrolling;
reset();
if (didScroll) {
fab.dataset.dragged = "1"; // suppress any click trailing the scroll
return;
}
if (!didDrag) return; // tap — let the click handler toggle the panel
fab.dataset.dragged = "1"; // suppress the click that trails this drag
const w = fab.offsetWidth, h = fab.offsetHeight;
const r = fab.getBoundingClientRect();
@@ -985,6 +1037,8 @@
applyFabPos(fab, side, y);
saveFabPos(side, y / window.innerHeight);
});
fab.addEventListener("pointercancel", reset);
}
// ============================================================