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.
This commit is contained in:
@@ -904,6 +904,7 @@
|
|||||||
const FAB_KEY = "mangabm:fabpos";
|
const FAB_KEY = "mangabm:fabpos";
|
||||||
const FAB_MARGIN = 12; // vertical breathing room at the top and bottom
|
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 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) {
|
function clamp(v, lo, hi) {
|
||||||
return Math.max(lo, Math.min(hi, v));
|
return Math.max(lo, Math.min(hi, v));
|
||||||
@@ -947,23 +948,69 @@
|
|||||||
applyFabPos(fab, side, y);
|
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) {
|
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) => {
|
fab.addEventListener("pointerdown", (e) => {
|
||||||
|
delete fab.dataset.dragged; // stale flag would swallow this gesture's click
|
||||||
down = true;
|
down = true;
|
||||||
moved = false;
|
moved = false;
|
||||||
|
armed = false;
|
||||||
|
scrolling = false;
|
||||||
|
fromHit = e.target.id === "hit";
|
||||||
sx = e.clientX;
|
sx = e.clientX;
|
||||||
sy = e.clientY;
|
sy = e.clientY;
|
||||||
|
lastY = e.clientY;
|
||||||
const r = fab.getBoundingClientRect();
|
const r = fab.getBoundingClientRect();
|
||||||
offX = e.clientX - r.left;
|
offX = e.clientX - r.left;
|
||||||
offY = e.clientY - r.top;
|
offY = e.clientY - r.top;
|
||||||
fab.setPointerCapture(e.pointerId);
|
fab.setPointerCapture(e.pointerId);
|
||||||
fab.style.transition = "none";
|
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) => {
|
fab.addEventListener("pointermove", (e) => {
|
||||||
if (!down) return;
|
if (!down) return;
|
||||||
if (!moved && (Math.abs(e.clientX - sx) > 6 || Math.abs(e.clientY - sy) > 6)) moved = true;
|
if (scrolling) {
|
||||||
if (!moved) return;
|
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();
|
e.preventDefault();
|
||||||
const w = fab.offsetWidth, h = fab.offsetHeight;
|
const w = fab.offsetWidth, h = fab.offsetHeight;
|
||||||
fab.style.left = clamp(e.clientX - offX, 0, window.innerWidth - w) + "px";
|
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.top = clamp(e.clientY - offY, 0, window.innerHeight - h) + "px";
|
||||||
fab.style.bottom = "auto";
|
fab.style.bottom = "auto";
|
||||||
});
|
});
|
||||||
|
|
||||||
fab.addEventListener("pointerup", (e) => {
|
fab.addEventListener("pointerup", (e) => {
|
||||||
if (!down) return;
|
if (!down) return;
|
||||||
down = false;
|
const didDrag = moved, didScroll = scrolling;
|
||||||
fab.style.transition = "";
|
reset();
|
||||||
if (!moved) return; // tap — let the click handler toggle the panel
|
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
|
fab.dataset.dragged = "1"; // suppress the click that trails this drag
|
||||||
const w = fab.offsetWidth, h = fab.offsetHeight;
|
const w = fab.offsetWidth, h = fab.offsetHeight;
|
||||||
const r = fab.getBoundingClientRect();
|
const r = fab.getBoundingClientRect();
|
||||||
@@ -985,6 +1037,8 @@
|
|||||||
applyFabPos(fab, side, y);
|
applyFabPos(fab, side, y);
|
||||||
saveFabPos(side, y / window.innerHeight);
|
saveFabPos(side, y / window.innerHeight);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fab.addEventListener("pointercancel", reset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user