drag autoscroll: add vertical (page) scroll + percentage edge zones (15%, 30-160px); v1.2

This commit is contained in:
2026-07-06 20:25:02 -03:00
parent 8760377927
commit fe577a674c
4 changed files with 94 additions and 41 deletions

View File

@@ -2,11 +2,20 @@
* TweakDrag -- auto-scroll the board while dragging a card near a screen edge.
*
* Kanboard drags cards with jQuery UI sortable. The board does not scroll during a drag
* (jQuery UI's autoscroll is unreliable horizontally, and on touch the bundled Touch Punch
* shim suppresses native scrolling while a card is held), so you can only drop onto columns
* already on screen. This scrolls #board-container while a card drag is active and the
* pointer nears the left/right edge, with a smooth continuous speed that grows toward the
* edge -- so you can drag a card to the edge and roll the board to a far column.
* (jQuery UI's autoscroll is unreliable, and on touch the bundled Touch Punch shim
* suppresses native scrolling while a card is held), so you can only drop where already on
* screen. While a card drag is active and the pointer nears an edge, this scrolls toward it
* with a smooth continuous speed that grows toward the edge:
*
* - Horizontal: scrolls #board-container (near its left/right edge).
* - Vertical: scrolls the window/page (near the viewport top/bottom). This matters when
* the vertical-collapse view is OFF and the whole page scrolls; when collapse is ON the
* page is not scrollable so window scrolling no-ops and the per-column scroll stays
* native.
*
* The edge zone is a percentage of each axis (clamped), so it adapts to screen size and
* orientation: a narrow phone width gets a small horizontal zone, a tall phone height a
* larger vertical one, a wide desktop the reverse -- with no device detection.
*
* It only acts during an active card drag near an edge, so it is harmless when idle. Works
* on desktop (mouse) and mobile (touch, via Touch Punch). No core changes.
@@ -14,16 +23,17 @@
(function () {
"use strict";
var EDGE_ZONE = 50; // px from the board edge where auto-scroll begins
var MAX_SPEED = 22; // px per animation frame at the very edge
var EASE = 1; // 1 = linear ramp, 2 = gentle-then-fast
var EDGE_ZONE_PCT = 0.15; // edge zone = 15% of the axis length...
var EDGE_ZONE_MIN = 30; // ...clamped to at least 30px...
var EDGE_ZONE_MAX = 160; // ...and at most 160px.
var MAX_SPEED = 22; // px per animation frame at the very edge
var EASE = 1; // 1 = linear ramp, 2 = gentle-then-fast
var jq = window.jQuery;
if (!jq) {
return;
}
var container = null;
var dragging = false;
var rafId = 0;
var lastX = 0;
@@ -39,9 +49,17 @@
}
}
// Distance is px from the edge; returns 0 at the zone border up to MAX_SPEED at the edge.
function speedFor(distance) {
var p = (EDGE_ZONE - distance) / EDGE_ZONE;
// Edge zone for an axis of the given length (px), 15% clamped to [30, 160].
function zoneFor(length) {
var z = length * EDGE_ZONE_PCT;
if (z < EDGE_ZONE_MIN) { z = EDGE_ZONE_MIN; }
if (z > EDGE_ZONE_MAX) { z = EDGE_ZONE_MAX; }
return z;
}
// Distance is px from the edge; 0 at the zone border up to MAX_SPEED at the edge.
function speedFor(distance, zone) {
var p = (zone - distance) / zone;
if (p <= 0) {
return 0;
}
@@ -55,42 +73,72 @@
}
function step() {
if (!dragging || !container) {
if (!dragging) {
return;
}
var rect = container.getBoundingClientRect();
var dLeft = lastX - rect.left;
var dRight = rect.right - lastX;
var speed = 0;
var moved = false;
if (dLeft < EDGE_ZONE) {
speed = -speedFor(dLeft);
} else if (dRight < EDGE_ZONE) {
speed = speedFor(dRight);
// Horizontal: scroll the board container near its left/right edge.
var c = document.getElementById("board-container");
if (c) {
var rect = c.getBoundingClientRect();
var zoneH = zoneFor(rect.width);
var hspeed = 0;
var dLeft = lastX - rect.left;
var dRight = rect.right - lastX;
if (dLeft < zoneH) {
hspeed = -speedFor(dLeft, zoneH);
} else if (dRight < zoneH) {
hspeed = speedFor(dRight, zoneH);
}
if (hspeed !== 0) {
var beforeX = c.scrollLeft;
c.scrollLeft = beforeX + hspeed;
if (c.scrollLeft !== beforeX) {
moved = true;
}
}
}
if (speed !== 0) {
var before = container.scrollLeft;
container.scrollLeft = before + speed;
// Vertical: scroll the window near the viewport top/bottom (collapse-off case;
// a natural no-op when the page is not scrollable).
var zoneV = zoneFor(window.innerHeight);
var vspeed = 0;
var dTop = lastY;
var dBottom = window.innerHeight - lastY;
// Only when the board actually moved: nudge jQuery UI to recompute drop
// positions so the placeholder follows onto the newly revealed column.
if (container.scrollLeft !== before) {
document.dispatchEvent(new MouseEvent("mousemove", {
clientX: lastX,
clientY: lastY,
bubbles: true
}));
if (dTop < zoneV) {
vspeed = -speedFor(dTop, zoneV);
} else if (dBottom < zoneV) {
vspeed = speedFor(dBottom, zoneV);
}
if (vspeed !== 0) {
var beforeY = window.pageYOffset;
window.scrollBy(0, vspeed);
if (window.pageYOffset !== beforeY) {
moved = true;
}
}
// When the board/page actually moved, nudge jQuery UI to recompute drop positions
// so the placeholder follows onto the newly revealed area (either axis).
if (moved) {
document.dispatchEvent(new MouseEvent("mousemove", {
clientX: lastX,
clientY: lastY,
bubbles: true
}));
}
rafId = window.requestAnimationFrame(step);
}
function startLoop() {
container = document.getElementById("board-container");
if (!container) {
if (!document.getElementById("board-container")) {
return;
}
dragging = true;