/** * Während eines Drags automatischen Bildlauf auslösen (Viewport + scrollbare Bereiche unter dem Cursor). * @param {DragEvent} e * @param {{ edgePx?: number, scrollStep?: number }} [opts] */ export function autoScrollForDragNearEdges(e, opts = {}) { const edge = opts.edgePx ?? 80 const step = opts.scrollStep ?? Math.max(24, Math.round(window.innerHeight * 0.05)) const { clientX, clientY } = e const vh = window.innerHeight || 0 const vw = window.innerWidth || 0 let sy = clientY < edge ? -step : vh > 0 && clientY > vh - edge ? step : 0 let sx = clientX < edge ? -step : vw > 0 && clientX > vw - edge ? step : 0 if (sy !== 0) window.scrollBy(0, sy) if (sx !== 0) window.scrollBy(sx, 0) /** @type {HTMLElement|null} */ let top = /** @type {HTMLElement|null} */ (document.elementFromPoint(clientX, clientY)) /** @type {Set} */ const done = new Set() /** @type {HTMLElement|null} */ let walk = top const innerEdge = Math.min(edge, 40) while (walk && walk !== document.body) { if (!(walk instanceof HTMLElement)) break const cs = window.getComputedStyle(walk) const canY = walk.scrollHeight - walk.clientHeight > 6 && (cs.overflowY === 'auto' || cs.overflowY === 'scroll') const canX = walk.scrollWidth - walk.clientWidth > 6 && (cs.overflowX === 'auto' || cs.overflowX === 'scroll') if ((canY || canX) && !done.has(walk)) { done.add(walk) const rect = walk.getBoundingClientRect() const relY = clientY - rect.top const relX = clientX - rect.left if (canY) { if (relY < innerEdge && walk.scrollTop > 0) walk.scrollTop -= step else if (rect.height - relY < innerEdge && walk.scrollTop < walk.scrollHeight - walk.clientHeight) { walk.scrollTop += step } } if (canX) { if (relX < innerEdge && walk.scrollLeft > 0) walk.scrollLeft -= step else if (rect.width - relX < innerEdge && walk.scrollLeft < walk.scrollWidth - walk.clientWidth) { walk.scrollLeft += step } } } walk = walk.parentElement } }