JavaScript拖拽手柄调整图形尺寸时位置偏移问题
拖拽调整图形尺寸时位置偏移问题修复
问题描述
开发JavaScript应用实现拖拽手柄调整图形尺寸功能时,拖拽顶部或左侧手柄会导致图形位置偏移。相关代码如下:
HTML代码
<div id="container"></div>
CSS代码
*, *::after, *::before { box-sizing: border-box; margin: 0; padding: 0; } body { height: 100vh; } #container { width: 100%; height: 100%; } .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: -5px; left: 50%; cursor: ns-resize; transform: translateX(-50%); } .handle-bottom { bottom: -5px; left: 50%; cursor: ns-resize; transform: translateX(-50%); } .handle-left { top: 50%; left: -5px; transform: translateY(-50%); cursor: ew-resize; } .handle-right { top: 50%; right: -5px; transform: translateY(-50%); cursor: ew-resize; }
原始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); }; 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"; shape.style.left = startX - newWidth + "px"; } else if (handleClass.includes("top")) { const newHeight = startHeight + startY - e.clientY; shape.style.height = newHeight + "px"; shape.style.top = startY - newHeight + "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 rect = element.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; } }); document.addEventListener("mousemove", (e) => { if (isDragging) { const newX = e.clientX - offsetX; const newY = e.clientY - offsetY; element.style.left = newX + "px"; element.style.top = newY + "px"; } }); document.addEventListener("mouseup", () => { isDragging = false; }); } }; const container = DrawJs.init("#container"); window.addEventListener("load", () => { DrawJs.drawRectangle(100, 60, 0, 0, "blue"); });
错误原因
处理左侧和顶部手柄的位置计算逻辑错误:
- 未记录图形初始的
left和top位置,仅依赖鼠标起始位置计算新位置,导致图形位置与手柄拖拽动作不同步。 - 左侧手柄的
left计算使用startX - newWidth,顶部手柄的top计算使用startY - newHeight,这两个公式未关联图形原始位置,必然导致偏移。
修复方案
- 在
startResize函数中,新增记录图形初始的startLeft和startTop值。 - 修改左侧和顶部手柄的位置计算逻辑,基于图形初始位置和鼠标移动差值更新位置,确保手柄拖拽时图形位置与手柄同步。
修复后的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, startLeft, startTop; 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); // 新增记录图形初始的left和top位置 startLeft = parseInt(document.defaultView.getComputedStyle(shape).left); startTop = parseInt(document.defaultView.getComputedStyle(shape).top); document.addEventListener("mousemove", resize); document.addEventListener("mouseup", stopResize); }; 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 widthDelta = startX - e.clientX; const newWidth = startWidth + widthDelta; shape.style.width = newWidth + "px"; // 基于初始left位置和鼠标移动差值更新left shape.style.left = startLeft - widthDelta + "px"; } else if (handleClass.includes("top")) { const heightDelta = startY - e.clientY; const newHeight = startHeight + heightDelta; shape.style.height = newHeight + "px"; // 基于初始top位置和鼠标移动差值更新top shape.style.top = startTop - heightDelta + "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 rect = element.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; } }); document.addEventListener("mousemove", (e) => { if (isDragging) { const newX = e.clientX - offsetX; const newY = e.clientY - offsetY; element.style.left = newX + "px"; element.style.top = newY + "px"; } }); document.addEventListener("mouseup", () => { isDragging = false; }); } }; const container = DrawJs.init("#container"); window.addEventListener("load", () => { DrawJs.drawRectangle(100, 60, 0, 0, "blue"); });
关键修改说明
- 在
startResize中新增startLeft和startTop,记录图形调整前的位置坐标。 - 左侧手柄:计算宽度变化量
widthDelta,新宽度为startWidth + widthDelta,同时将left设置为startLeft - widthDelta,确保图形左侧随手柄拖拽同步移动。 - 顶部手柄:计算高度变化量
heightDelta,新高度为startHeight + heightDelta,同时将top设置为startTop - heightDelta,确保图形顶部随手柄拖拽同步移动。
内容的提问来源于stack exchange,提问作者Dinesh Basnet
相关产品推荐
相关产品推荐

