weirsoe-ui.js: mobile drag lifts a floating clone + wider auto-scroll zone

Touch drag now lifts the card into a position:fixed .wt-drag-clone (shadow +
scale) that follows the finger, with a dimmed placeholder reordering underneath
— reads as actually moving the card. Edge auto-scroll zone widened to 130px with
a minimum speed so it reliably scrolls on mobile.
This commit is contained in:
Asger Geel Weirsøe
2026-06-14 18:40:00 +02:00
parent a04a3c0970
commit 4388f02f18
2 changed files with 44 additions and 14 deletions
+9 -1
View File
@@ -170,7 +170,15 @@ body::before {
}
.drag-handle:active { cursor: grabbing; }
[data-wt-sortable] > [data-item-id] { cursor: default; }
.wt-dragging { opacity: 0.45; }
.wt-dragging { opacity: 0.4; }
/* The floating clone that follows the finger on touch (the "popped-out" card). */
.wt-drag-clone {
position: fixed; left: 0; top: 0; z-index: 9999;
margin: 0 !important; pointer-events: none; will-change: transform;
border-radius: var(--wt-radius);
box-shadow: 0 18px 46px #000000aa;
opacity: 0.98; transition: none;
}
/* Pill chip / tag-link (e.g. blog tags) */
.wt-chip {
+35 -13
View File
@@ -95,10 +95,12 @@
else container.insertBefore(dragEl, after);
});
// ---- mobile: long-press to lift, drag to reorder, edge auto-scroll ----
// (native DnD doesn't fire on touch). Hold ~220ms to lift; a scroll-intent
// move before that cancels the lift so the page still scrolls normally.
var holdTimer = null, lifted = false, touchEl = null, startY = 0, scrollSpeed = 0, raf = null;
// ---- mobile: long-press lifts the card into a floating clone that follows
// the finger; the list reorders underneath; holding near a screen edge
// auto-scrolls. (Native DnD doesn't fire on touch.) Hold ~220ms to lift; a
// scroll-intent move before that cancels the lift so the page scrolls. ----
var holdTimer = null, lifted = false, touchEl = null, clone = null;
var startY = 0, offX = 0, offY = 0, scrollSpeed = 0, raf = null;
function startScroll() {
if (raf) return;
@@ -109,8 +111,24 @@
raf = requestAnimationFrame(tick);
}
function stopScroll() { if (raf) { cancelAnimationFrame(raf); raf = null; } scrollSpeed = 0; }
function makeClone(el, x, y) {
var r = el.getBoundingClientRect();
offX = x - r.left; offY = y - r.top;
clone = el.cloneNode(true);
clone.classList.remove('wt-dragging');
clone.classList.add('wt-drag-clone');
clone.style.width = r.width + 'px';
clone.style.transform = 'translate(' + (x - offX) + 'px,' + (y - offY) + 'px) scale(1.04)';
document.body.appendChild(clone);
}
function moveClone(x, y) {
if (clone) clone.style.transform = 'translate(' + (x - offX) + 'px,' + (y - offY) + 'px) scale(1.04)';
}
function endTouch() {
if (holdTimer) { clearTimeout(holdTimer); holdTimer = null; }
if (clone && clone.parentNode) clone.parentNode.removeChild(clone);
clone = null;
if (lifted && touchEl) { touchEl.classList.remove('wt-dragging'); persist(container); }
lifted = false; touchEl = null; stopScroll();
}
@@ -118,11 +136,13 @@
container.querySelectorAll('[data-item-id]').forEach(function (el) {
el.addEventListener('touchstart', function (e) {
if (e.touches.length !== 1) return;
// don't hijack taps on interactive controls
if (e.target.closest('a, button, input, select, textarea, summary, label')) return;
startY = e.touches[0].clientY;
var t = e.touches[0];
startY = t.clientY;
holdTimer = setTimeout(function () {
lifted = true; touchEl = el; el.classList.add('wt-dragging');
lifted = true; touchEl = el;
el.classList.add('wt-dragging');
makeClone(el, t.clientX, t.clientY);
if (navigator.vibrate) navigator.vibrate(12);
startScroll();
}, 220);
@@ -137,14 +157,16 @@
return;
}
if (!touchEl) return;
e.preventDefault(); // we own the gesture; stop the page from scrolling under us
var y = e.touches[0].clientY;
var after = afterElement(container, y);
e.preventDefault(); // we own the gesture now
var t = e.touches[0];
moveClone(t.clientX, t.clientY);
var after = afterElement(container, t.clientY);
if (after == null) container.appendChild(touchEl);
else container.insertBefore(touchEl, after);
var edge = 90, vh = window.innerHeight;
if (y < edge) scrollSpeed = -Math.ceil((edge - y) / 5);
else if (y > vh - edge) scrollSpeed = Math.ceil((y - (vh - edge)) / 5);
// edge auto-scroll — generous zone so it triggers well before the edge
var edge = 130, vh = window.innerHeight;
if (t.clientY < edge) scrollSpeed = -Math.max(6, Math.ceil((edge - t.clientY) / 4));
else if (t.clientY > vh - edge) scrollSpeed = Math.max(6, Math.ceil((t.clientY - (vh - edge)) / 4));
else scrollSpeed = 0;
}, { passive: false });