You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在拖拽元素时扩大默认滚动触发区域?

问题

我有一个可拖拽排序的元素列表,当前拖拽时只能通过将鼠标停在屏幕顶部和底部极小的30px区域触发滚动,想把触发滚动的区域扩大到120px。

相关代码

HTML代码

<div id='mainparent'> <!-- 主界面容器,滚动发生在这里 -->
  <div id='DivTesting'> <!-- 包含可重复元素的父容器 -->
    <div id='SavedIdeaDivContainer1'> <!-- 可拖拽排序的重复元素1 -->
    </div>
    <div id='SavedIdeaDivContainer2'> <!-- 这类元素有很多,数十个以上 -->
    </div>
    <div id='SavedIdeaDivContainer3'>
    </div>
  </div>
  <div id="itemClip" class="item itemClip hide"></div>
</div>

原始JavaScript代码

var elements = document.querySelectorAll('.box .item');

var targetEl;
var wrapper = document.querySelector(".box");
var itemClip = document.getElementById("itemClip");

var scopeObj;

// === 事件绑定 ===
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);
}

// === 函数集合 ===

function handleDrag(event) {
  targetEl = event.target;
  targetEl.classList.add("onDrag");
}

function handleDragEnd(event) {
  targetEl.classList.remove("onDrag");
}

function handleDragEnter(event) {
  wrapper.insertBefore(targetEl, event.target.closest(".item"));
}

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");
}

function handleTouchMove(event) {
  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);
}

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);
  }
}

修改后的解决方案代码

以下是添加了120px滚动触发区域逻辑的完整JavaScript代码:

var elements = document.querySelectorAll('.box .item');

var targetEl;
var wrapper = document.querySelector(".box");
var itemClip = document.getElementById("itemClip");
var scopeObj;
// 配置:滚动触发区域高度(修改这里即可调整范围)
var SCROLL_TRIGGER_ZONE = 120;
// 配置:滚动速度
var SCROLL_SPEED = 8;
// 滚动定时器,控制连续滚动
var scrollTimer;

// === 事件绑定 ===
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);
}

// === 函数集合 ===
function handleDrag(event) {
  targetEl = event.target;
  targetEl.classList.add("onDrag");
  // 拖拽开始时监听鼠标移动,用于检测滚动触发
  document.addEventListener("mousemove", checkScrollOnDrag);
}

function handleDragEnd(event) {
  targetEl.classList.remove("onDrag");
  // 拖拽结束时移除监听并清除滚动定时器
  document.removeEventListener("mousemove", checkScrollOnDrag);
  clearInterval(scrollTimer);
}

function handleDragEnter(event) {
  wrapper.insertBefore(targetEl, event.target.closest(".item"));
}

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");
  // 触摸结束时清除滚动定时器
  clearInterval(scrollTimer);
}

function handleTouchMove(event) {
  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);
  // 触摸移动时检测滚动触发
  checkScrollOnTouch(event.changedTouches[0].clientY);
}

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);
  }
}

// 鼠标拖拽时检测滚动触发
function checkScrollOnDrag(e) {
  checkScrollZone(e.clientY);
}

// 触摸拖拽时检测滚动触发
function checkScrollOnTouch(clientY) {
  checkScrollZone(clientY);
}

// 核心逻辑:检测位置是否在滚动区域,执行滚动并更新元素范围
function checkScrollZone(clientY) {
  var mainParent = document.getElementById('mainparent');
  // 清除之前的定时器,避免重复触发
  clearInterval(scrollTimer);
  
  // 顶部触发区域:距离屏幕顶部0到SCROLL_TRIGGER_ZONE像素
  if (clientY <= SCROLL_TRIGGER_ZONE) {
    scrollTimer = setInterval(function() {
      mainParent.scrollTop -= SCROLL_SPEED;
      // 滚动后更新元素位置范围,确保排序逻辑正常
      defineScope(elements);
    }, 20);
  } 
  // 底部触发区域:距离屏幕底部0到SCROLL_TRIGGER_ZONE像素
  else if (clientY >= window.innerHeight - SCROLL_TRIGGER_ZONE) {
    scrollTimer = setInterval(function() {
      mainParent.scrollTop += SCROLL_SPEED;
      // 滚动后更新元素位置范围,确保排序逻辑正常
      defineScope(elements);
    }, 20);
  }
}

关键改动说明

  • 新增配置常量:SCROLL_TRIGGER_ZONE = 120,直接定义触发滚动的区域高度,后续调整只需修改这个值。
  • 添加滚动监听:鼠标拖拽时绑定mousemove事件,触摸拖拽时在touchmove中触发检测,确保两种拖拽方式都支持滚动触发。
  • 连续滚动控制:使用定时器实现平滑连续滚动,滚动时同步更新scopeObj(元素位置范围),保证拖拽排序逻辑不受滚动影响。
  • 边界判断逻辑:通过window.innerHeight计算底部触发区域,确保在不同屏幕尺寸下都能正确识别底部120px范围。

内容的提问来源于stack exchange,提问作者king_anton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 19:10:34