JavaScript拖拽元素时偏离鼠标指针的异常问题排查
拖拽元素偏离鼠标指针的问题修复
实现了基于mousedown/mousemove/mouseup的可拖拽元素功能,但拖拽时元素会异常偏离鼠标指针。点击黑色矩形工具按钮创建矩形后,拖拽即可复现问题。
问题分析
- 坐标计算逻辑错误:当前代码使用元素的视口绝对位置(
getBoundingClientRect().left)计算偏移,但元素是相对容器绝对定位,应该基于容器的视口位置修正坐标。 - 事件干扰未处理:
mousedown事件未阻止冒泡和默认行为,可能触发其他元素的事件处理,干扰拖拽逻辑。 - 相对坐标转换缺失:拖拽时直接用视口坐标设置元素的
left/top,未转换为容器内的相对坐标,导致位置偏移。
修复方案
- 修正偏移计算:在
mousedown时,先获取容器的视口位置,将鼠标坐标转换为容器内的相对坐标后,再计算鼠标与元素的偏移量。 - 阻止事件干扰:在
mousedown回调中添加e.preventDefault()和e.stopPropagation(),避免事件冒泡和默认行为影响。 - 调整拖拽坐标:拖拽时基于容器的视口位置,计算鼠标在容器内的相对坐标,减去偏移量得到元素的目标位置。
修改后的完整代码
JavaScript代码
const DrawJs = { selectedShape: null, offsetX: 0, offsetY: 0, container: null, resizing: false, init: function (containerId) { const container = document.querySelector(containerId); this.container = container; return container; }, drawRectangle: function (width, height, x, y, color) { const shape = document.createElement("div"); shape.classList.add("resizable-shape"); shape.style.width = width + "px"; shape.style.height = height + "px"; shape.style.left = x + "px"; shape.style.top = y + "px"; shape.style.backgroundColor = color; this.createHandle("handle-top", shape); this.createHandle("handle-bottom", shape); this.createHandle("handle-left", shape); this.createHandle("handle-right", shape); this.draggable(shape); this.container.appendChild(shape); }, createHandle: function (handleClass, shape) { const handle = document.createElement("div"); handle.classList.add("resizable-handle", handleClass); let isResizing = false; let startX, startY, startWidth, startHeight; const startResize = (e) => { isResizing = true; startX = e.clientX; startY = e.clientY; startWidth = parseInt(document.defaultView.getComputedStyle(shape).width); startHeight = parseInt(document.defaultView.getComputedStyle(shape).height); document.addEventListener("mousemove", resize); document.addEventListener("mouseup", stopResize); // 阻止事件冒泡和默认行为 e.preventDefault(); e.stopPropagation(); }; const resize = (e) => { if (isResizing) { if (handleClass.includes("right")) { shape.style.width = startWidth + e.clientX - startX + "px"; } else if (handleClass.includes("bottom")) { shape.style.height = startHeight + e.clientY - startY + "px"; } else if (handleClass.includes("left")) { const newWidth = startWidth + startX - e.clientX; shape.style.width = newWidth + "px"; // 修正左侧拖拽的left计算,基于容器相对位置 const containerRect = this.container.getBoundingClientRect(); shape.style.left = (startX - newWidth - containerRect.left) + "px"; } else if (handleClass.includes("top")) { const newHeight = startHeight + startY - e.clientY; shape.style.height = newHeight + "px"; // 修正顶部拖拽的top计算,基于容器相对位置 const containerRect = this.container.getBoundingClientRect(); shape.style.top = (startY - newHeight - containerRect.top) + "px"; } } }; const stopResize = () => { isResizing = false; document.removeEventListener("mousemove", resize); document.removeEventListener("mouseup", stopResize); }; handle.addEventListener("mousedown", startResize); shape.appendChild(handle); }, draggable: function (element) { let isDragging = false; let offsetX, offsetY; element.addEventListener("mousedown", (e) => { if (!this.resizing) { isDragging = true; const containerRect = this.container.getBoundingClientRect(); const elementLeft = parseInt(element.style.left); const elementTop = parseInt(element.style.top); // 计算鼠标在容器内的相对坐标,再减去元素的相对位置得到偏移 const mouseXInContainer = e.clientX - containerRect.left; const mouseYInContainer = e.clientY - containerRect.top; offsetX = mouseXInContainer - elementLeft; offsetY = mouseYInContainer - elementTop; // 阻止事件冒泡和默认行为 e.preventDefault(); e.stopPropagation(); } }); document.addEventListener("mousemove", (e) => { if (isDragging) { const containerRect = this.container.getBoundingClientRect(); // 计算鼠标在容器内的相对坐标,减去偏移得到元素目标位置 const mouseXInContainer = e.clientX - containerRect.left; const mouseYInContainer = e.clientY - containerRect.top; const newX = mouseXInContainer - offsetX; const newY = mouseYInContainer - offsetY; element.style.left = newX + "px"; element.style.top = newY + "px"; } }); document.addEventListener("mouseup", () => { isDragging = false; }); } }; const container = DrawJs.init("#container"); document.getElementById("rect-tool").addEventListener("click", () => { DrawJs.drawRectangle(100, 60, 300, 200, "#000"); });
CSS代码(无需修改)
*, *::after, *::before { margin: 0; padding: 0; box-sizing: border-box; } body { display: grid; place-content: center; } #container { width: calc(50vw); height: 200px; border: 1px solid #000; margin-top: 40px; position: relative; } #tool-wrapper { margin-top: 20px; display: flex; align-items: center; justify-content: flex-start; gap: 14px; } .tool { width: 40px; height: 40px; border: 1px solid gray; list-style: none; cursor: pointer; display: grid; place-content: center; border-radius: 4px; } #rect-tool > div { width: 30px; height: 20px; background: #000; } #circle-tool > div { width: 24px; height: 24px; border-radius: 50%; background: #000; } #line-tool > div { width: 35px; height: 4px; border-radius: 100px; transform: rotate(-45deg); background: #000; } #polygon-tool > div { width: 0; height: 0; border-top: 16px solid transparent; border-left: 16px solid transparent; border-bottom: 16px solid #000; border-right: 16px solid transparent; transform: translateY(-25%); } .resizable-shape { position: absolute; } .resizable-handle { width: 10px; height: 10px; background-color: #fff; position: absolute; cursor: pointer; border-radius: 50%; border: 1px solid #000; display: none; } .resizable-shape:hover .resizable-handle { display: block; } .handle-top { top: 0; left: 50%; cursor: ns-resize; transform: translate(-50%, -50%); } .handle-bottom { bottom: 0; left: 50%; cursor: ns-resize; transform: translate(-50%, 50%); } .handle-left { top: 50%; left: 0; transform: translate(-50%, -50%); cursor: ew-resize; } .handle-right { top: 50%; right: 0; transform: translate(50%, -50%); cursor: ew-resize; }
HTML代码(无需修改)
<div id="container"></div> <ul id="tool-wrapper"> <li class="tool" id="rect-tool" title="rectangle"> <div></div> </li> </ul>
内容的提问来源于stack exchange,提问作者Dinesh Basnet
相关产品推荐
相关产品推荐

