如何在拖拽排序时增大触发滚动的区域范围?
解决方案:扩大拖拽滚动触发区域
要解决拖拽时滚动触发区域过窄的问题,我们可以自定义滚动逻辑,将列表容器顶部和底部各100px的区域设为滚动触发区,同时优化滚动流畅性。以下是具体修改步骤:
1. 添加滚动配置变量
在代码开头添加全局配置项,方便后续调整滚动参数:
// 滚动行为配置 const SCROLL_TRIGGER_AREA = 100; // 触发滚动的边缘区域高度(单位:px) const SCROLL_SPEED = 6; // 滚动速度值,数值越大滚动越快 let scrollTimer = null; // 控制持续滚动的定时器
2. 绑定滚动监听事件
在事件绑定部分,给列表容器(.box)新增dragover事件监听,用于处理鼠标拖拽时的滚动逻辑:
// 原事件绑定代码后添加 wrapper.addEventListener("dragover", handleDragOver);
3. 实现鼠标拖拽滚动逻辑
新增handleDragOver函数,根据鼠标位置判断是否触发滚动:
function handleDragOver(event) { event.preventDefault(); // 必须阻止默认行为,确保拖拽排序正常工作 const containerRect = wrapper.getBoundingClientRect(); const mouseY = event.clientY; // 清除之前的定时器,避免重复触发滚动 if (scrollTimer) clearInterval(scrollTimer); // 顶部触发区:鼠标进入容器顶部100px范围时向上滚动 if (mouseY <= containerRect.top + SCROLL_TRIGGER_AREA) { scrollTimer = setInterval(() => { wrapper.scrollTop -= SCROLL_SPEED; // 滚动到容器顶部后停止 if (wrapper.scrollTop <= 0) clearInterval(scrollTimer); }, 16); // 按60fps频率执行,保证滚动流畅 } // 底部触发区:鼠标进入容器底部100px范围时向下滚动 else if (mouseY >= containerRect.bottom - SCROLL_TRIGGER_AREA) { scrollTimer = setInterval(() => { wrapper.scrollTop += SCROLL_SPEED; // 滚动到容器底部后停止 if (wrapper.scrollTop >= wrapper.scrollHeight - wrapper.clientHeight) clearInterval(scrollTimer); }, 16); } }
4. 优化拖拽结束后的清理逻辑
修改handleDragEnd和handleTouchEnd函数,确保拖拽结束后立即停止滚动:
// 修改鼠标拖拽结束函数 function handleDragEnd(event) { targetEl.classList.remove("onDrag"); // 清除滚动定时器 if (scrollTimer) { clearInterval(scrollTimer); scrollTimer = null; } } // 修改触摸拖拽结束函数 function handleTouchEnd(event) { itemClip.classList.add("hide"); targetEl.classList.remove("onDrag"); // 清除滚动定时器 if (scrollTimer) { clearInterval(scrollTimer); scrollTimer = null; } }
5. 实现触摸拖拽的滚动逻辑
修改handleTouchMove函数,添加触摸位置判断和滚动触发逻辑:
function handleTouchMove(event) { event.preventDefault(); // 阻止页面默认滚动行为 itemClip.style.top = event.changedTouches[0].clientY + "px"; itemClip.style.left = event.changedTouches[0].clientX + "px"; hitTest(event.changedTouches[0].clientX, event.changedTouches[0].clientY); const containerRect = wrapper.getBoundingClientRect(); const touchY = event.changedTouches[0].clientY; // 清除之前的定时器 if (scrollTimer) clearInterval(scrollTimer); // 顶部触摸触发区 if (touchY <= containerRect.top + SCROLL_TRIGGER_AREA) { scrollTimer = setInterval(() => { wrapper.scrollTop -= SCROLL_SPEED; if (wrapper.scrollTop <= 0) clearInterval(scrollTimer); }, 16); } // 底部触摸触发区 else if (touchY >= containerRect.bottom - SCROLL_TRIGGER_AREA) { scrollTimer = setInterval(() => { wrapper.scrollTop += SCROLL_SPEED; if (wrapper.scrollTop >= wrapper.scrollHeight - wrapper.clientHeight) clearInterval(scrollTimer); }, 16); } }
修改后的完整代码
var elements = document.querySelectorAll('.box .item'); var targetEl; var wrapper = document.querySelector(".box"); var itemClip = document.getElementById("itemClip"); var scopeObj; // 滚动行为配置 const SCROLL_TRIGGER_AREA = 100; // 触发滚动的边缘区域高度(单位:px) const SCROLL_SPEED = 6; // 滚动速度值,数值越大滚动越快 let scrollTimer = null; // 控制持续滚动的定时器 // === Event Binding === for (var j = 0, max = elements.length; j < max; j++) { elements[j].addEventListener("dragstart", handleDrag); elements[j].addEventListener("dragend", handleDragEnd); elements[j].addEventListener("dragenter", handleDragEnter); elements[j].addEventListener("touchstart", handleTouch); elements[j].addEventListener("touchend", handleTouchEnd); elements[j].addEventListener("touchmove", handleTouchMove); } // 添加滚动监听 wrapper.addEventListener("dragover", handleDragOver); // === Function Kits === function handleDrag(event) { targetEl = event.target; targetEl.classList.add("onDrag"); } function handleDragEnd(event) { targetEl.classList.remove("onDrag"); // 清除滚动定时器 if (scrollTimer) { clearInterval(scrollTimer); scrollTimer = null; } } function handleDragEnter(event) { wrapper.insertBefore(targetEl, event.target.closest(".item")); } function handleDragOver(event) { event.preventDefault(); // 必须阻止默认行为,确保拖拽排序正常工作 const containerRect = wrapper.getBoundingClientRect(); const mouseY = event.clientY; // 清除之前的定时器,避免重复触发滚动 if (scrollTimer) clearInterval(scrollTimer); // 顶部触发区:鼠标进入容器顶部100px范围时向上滚动 if (mouseY <= containerRect.top + SCROLL_TRIGGER_AREA) { scrollTimer = setInterval(() => { wrapper.scrollTop -= SCROLL_SPEED; // 滚动到容器顶部后停止 if (wrapper.scrollTop <= 0) clearInterval(scrollTimer); }, 16); // 按60fps频率执行,保证滚动流畅 } // 底部触发区:鼠标进入容器底部100px范围时向下滚动 else if (mouseY >= containerRect.bottom - SCROLL_TRIGGER_AREA) { scrollTimer = setInterval(() => { wrapper.scrollTop += SCROLL_SPEED; // 滚动到容器底部后停止 if (wrapper.scrollTop >= wrapper.scrollHeight - wrapper.clientHeight) clearInterval(scrollTimer); }, 16); } } function handleTouch(event) { defineScope(elements); targetEl = event.target; itemClip.style.top = event.changedTouches[0].clientY + "px"; itemClip.style.left = event.changedTouches[0].clientX + "px"; itemClip.innerText = event.target.innerText; itemClip.classList.remove("hide"); targetEl.classList.add("onDrag"); } function handleTouchEnd(event) { itemClip.classList.add("hide"); targetEl.classList.remove("onDrag"); // 清除滚动定时器 if (scrollTimer) { clearInterval(scrollTimer); scrollTimer = null; } } function handleTouchMove(event) { event.preventDefault(); // 阻止页面默认滚动行为 itemClip.style.top = event.changedTouches[0].clientY + "px"; itemClip.style.left = event.changedTouches[0].clientX + "px"; hitTest(event.changedTouches[0].clientX, event.changedTouches[0].clientY); const containerRect = wrapper.getBoundingClientRect(); const touchY = event.changedTouches[0].clientY; // 清除之前的定时器 if (scrollTimer) clearInterval(scrollTimer); // 顶部触摸触发区 if (touchY <= containerRect.top + SCROLL_TRIGGER_AREA) { scrollTimer = setInterval(() => { wrapper.scrollTop -= SCROLL_SPEED; if (wrapper.scrollTop <= 0) clearInterval(scrollTimer); }, 16); } // 底部触摸触发区 else if (touchY >= containerRect.bottom - SCROLL_TRIGGER_AREA) { scrollTimer = setInterval(() => { wrapper.scrollTop += SCROLL_SPEED; if (wrapper.scrollTop >= wrapper.scrollHeight - wrapper.clientHeight) clearInterval(scrollTimer); }, 16); } } function hitTest(thisX, thisY) { for (var j = 0, max = scopeObj.length; j < max; j++) { if (thisX > scopeObj[j].startX && thisX < scopeObj[j].endX) { if (thisY > scopeObj[j].startY && thisY < scopeObj[j].endY) { wrapper.insertBefore(targetEl, scopeObj[j].target); return; } } } } function defineScope(elementArray) { scopeObj = []; for (var j = 0, max = elementArray.length; j < max; j++) { var newObj = {}; newObj.target = elementArray[j]; newObj.startX = elementArray[j].offsetLeft; newObj.endX = elementArray[j].offsetLeft + elementArray[j].offsetWidth; newObj.startY = elementArray[j].offsetTop; newObj.endY = elementArray[j].offsetTop + elementArray[j].offsetHeight; scopeObj.push(newObj); } }
关键说明
- 自定义滚动逻辑替代浏览器默认的窄区域触发,将滚动触发范围扩大到顶部/底部100px,贴合用户操作习惯
- 采用60fps频率的定时器控制滚动,避免卡顿,提升流畅度
- 拖拽结束时自动清除定时器,防止滚动行为失控
- 同时兼容鼠标拖拽和触摸拖拽两种交互场景
内容的提问来源于stack exchange,提问作者king_anton
相关产品推荐
相关产品推荐

