用鼠标事件实现自定义拖拽
用鼠标事件实现拖拽的骨架是什么?
- 三步:
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大多是这套东西的包装;