如何获取Sortable中Draggable相对于触发isOver的Droppable的位置?
实现Notion风格的拖拽嵌套/排序逻辑
要实现Notion那种拖拽时靠近元素中心就嵌套、靠近上下边缘就调整排序的效果,核心是精准计算鼠标在目标元素上的相对位置,下面是具体实现方案:
核心思路
- 拖拽悬停时,基于鼠标的实时视口坐标和目标元素的视口位置,计算相对距离
- 放弃依赖拖拽起始偏移量(
delta),改用clientX/clientY+getBoundingClientRect(),彻底解决滚动时的位置偏差问题
关键计算步骤
- 获取目标元素的视口位置:用
getBoundingClientRect()拿到元素的top、height等实时视口坐标,滚动时这个值会自动更新 - 计算目标元素的垂直中心:
centerY = rect.top + rect.height / 2 - 判断鼠标位置归属:
- 计算鼠标到中心的距离占元素半高的比例:
distanceRatio = Math.abs(clientY - centerY) / (rect.height / 2) - 若比例≤0.5(即靠近中心区域):将拖拽元素设为目标元素的子元素
- 若比例>0.5且鼠标在中心上方:把拖拽元素插入到目标元素的前面
- 若比例>0.5且鼠标在中心下方:把拖拽元素插入到目标元素的后面
- 计算鼠标到中心的距离占元素半高的比例:
代码示例
function handleDragMove(event, overElement) { const { clientY } = event; const rect = overElement.getBoundingClientRect(); const centerY = rect.top + rect.height / 2; const halfHeight = rect.height / 2; const distanceToCenter = Math.abs(clientY - centerY); // 接近度:0~1,值越大越靠近中心 const proximity = 1 - (distanceToCenter / halfHeight); if (proximity >= 0.5) { // 触发嵌套逻辑:将拖拽元素设为目标的子元素 overElement.appendChild(draggableElement); } else { if (clientY < centerY) { // 插入到目标元素前面 overElement.parentNode.insertBefore(draggableElement, overElement); } else { // 插入到目标元素后面 overElement.parentNode.insertBefore(draggableElement, overElement.nextSibling); } } }
注意事项
- 只在
dragover事件中执行计算,避免频繁触发 - 嵌套场景下要先判断目标元素是否允许作为父容器(比如是否是文件夹类型)
- 可以微调
0.5这个阈值,让嵌套触发的区域更宽或更窄,匹配你想要的交互手感
内容的提问来源于stack exchange,提问作者user22191766
相关产品推荐
相关产品推荐

