From 4388f02f18e5f7bb991abf903d4f8a97dee5684c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asger=20Geel=20Weirs=C3=B8e?= Date: Sun, 14 Jun 2026 18:40:00 +0200 Subject: [PATCH] weirsoe-ui.js: mobile drag lifts a floating clone + wider auto-scroll zone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- weirsoe-theme.css | 10 +++++++++- weirsoe-ui.js | 48 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/weirsoe-theme.css b/weirsoe-theme.css index 413f122..0fb0d64 100644 --- a/weirsoe-theme.css +++ b/weirsoe-theme.css @@ -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 { diff --git a/weirsoe-ui.js b/weirsoe-ui.js index b262e75..f7a71b4 100644 --- a/weirsoe-ui.js +++ b/weirsoe-ui.js @@ -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 });