From cbb8d6727610d2ad168fba48a5054e5a2938260c Mon Sep 17 00:00:00 2001 From: Sulthan Zaki Date: Tue, 28 Jul 2026 20:10:29 +0700 Subject: [PATCH] feat(userscript): scroll or reposition from the widened tap strip touch-action is resolved at gesture start, so the strip cannot be both browser-scrolled and script-dragged. Own the gesture and split by intent: a swipe from the strip scrolls the page, a ~400ms hold arms a reposition drag, and the visible sliver still drags with no hold. Adds the missing pointercancel reset. --- userscript/manga-bookmark.user.js | 66 ++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/userscript/manga-bookmark.user.js b/userscript/manga-bookmark.user.js index 1c56d2f..607ee13 100644 --- a/userscript/manga-bookmark.user.js +++ b/userscript/manga-bookmark.user.js @@ -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); } // ============================================================