跳到主要内容

用鼠标事件实现自定义拖拽

用鼠标事件实现拖拽的骨架是什么?​

  • 三步: mousedown 准备 (设 position: absolute、z-index: 1000、挪到 body 下) → document.mousemove 跟手 → mouseup 收尾;
  • 监听位置: mousemove 必须挂 document, 不能挂被拖元素; 该事件不覆盖每个像素, 快速移动后指针会跳到元素外甚至窗口外;
  • 原生冲突: 浏览器自带图片拖拽会与自定义实现打架, 表现为拖动开始时冒出"分身", 用 ondragstart 返回 false 关掉;
ball.onmousedown = function (event) {
ball.style.position = "absolute";
ball.style.zIndex = 1000;
document.body.append(ball);

function moveAt(pageX, pageY) {
ball.style.left = pageX - ball.offsetWidth / 2 + "px";
ball.style.top = pageY - ball.offsetHeight / 2 + "px";
}
moveAt(event.pageX, event.pageY);

function onMouseMove(event) {
moveAt(event.pageX, event.pageY);
}
document.addEventListener("mousemove", onMouseMove);

ball.onmouseup = function () {
document.removeEventListener("mousemove", onMouseMove);
ball.onmouseup = null;
};
};

ball.ondragstart = () => false;

如何让被拖元素不跳位?​

  • 问题: 把元素中心对到指针下, 从边缘抓起时会瞬间跳到指针中心;
  • 做法: mousedown 时记下指针相对元素左上角的偏移, 全程保持该偏移;
  • 取偏移: let shiftX = event.clientX - ball.getBoundingClientRect().left; (同 shiftY);
  • 定位: ball.style.left = event.pageX - shiftX + "px";

如何确定并高亮指针下方的放置目标?​

  • 失败方案: 给下层元素挂 mouseover/mouseup; 被拖元素始终在最上层, 鼠标事件只命中顶层元素;
  • 工具: document.elementFromPoint(clientX, clientY) 返回该坐标处最内层 (即最上层) 元素, 出窗口返回 null;
  • 前提: 调用前先 ball.hidden = true 临时隐藏被拖元素, 否则只会返回它自己;
  • 比较: 用 elemBelow.closest('.droppable') 得到候选, 与上次的 currentDroppable 比较, 由 null 与非 null 的切换触发进入/离开;
let currentDroppable = null;

function onMouseMove(event) {
moveAt(event.pageX, event.pageY);

ball.hidden = true;
const elemBelow = document.elementFromPoint(event.clientX, event.clientY);
ball.hidden = false;
if (!elemBelow) return; // 拖到窗口外

const droppableBelow = elemBelow.closest(".droppable");
if (currentDroppable !== droppableBelow) {
if (currentDroppable) leaveDroppable(currentDroppable);
currentDroppable = droppableBelow;
if (currentDroppable) enterDroppable(currentDroppable);
}
}

这套骨架还能扩展出什么?​

  • mouseup 时真正落地: 改数据、搬动元素;
  • 高亮途经的放置目标;
  • 限制拖动区域或方向;
  • 事件委托: mousedown/mouseup 里查 event.target, 一套 handler 管成百上千个元素;
  • 框架里的 DragZone/Droppable/Draggable 大多是这套东西的包装;