Canvas拖拽矩形异常:拖拽批量生成矩形后停止问题排查
看起来你遇到的核心问题是拖拽按钮生成矩形时短时间内大量创建矩形,随后操作卡顿甚至停止,这主要是由于事件绑定错误、循环初始化问题以及重复绑定事件导致的,我们一步步来拆解修复:
1. 最关键的错误:事件绑定类型错误+重复绑定
在handleMousedown函数中,你绑定了错误的事件类型:
window.addEventListener("onclick", handleMousemoveRectangle);
这里应该绑定"mousemove"而不是"onclick",而且每次点击按钮都会重复绑定这个事件(以及mouseup事件),导致后续鼠标移动时,handleMousemoveRectangle被触发几十甚至上百次,不仅会重复修改矩形位置,还会因为频繁操作Canvas导致浏览器卡顿,甚至假死。
另外,在点击已有矩形的逻辑中,你也没有先移除旧的事件监听就直接绑定新的,同样会造成重复绑定。
修复方式:
- 绑定正确的
"mousemove"事件 - 在绑定新事件前,先移除旧的监听,避免重复绑定
- 把事件绑定的逻辑整理得更严谨,确保每个事件只绑定一次
2. Update函数中的循环初始化错误
你的update函数里的for循环没有初始化变量i:
for (let i: number; i < rectangleArray.length; i++) {
这里i默认是undefined,循环条件undefined < rectangleArray.length永远为false,导致已有的矩形根本不会被绘制。这会让你误以为矩形没有被创建,但实际上rectangleArray里已经堆了大量的矩形对象,进一步加重性能问题。
修复方式:初始化i为0:
for (let i = 0; i < rectangleArray.length; i++) {
3. 点击按钮时重复创建矩形
当你按住鼠标在按钮上拖拽时,可能因为事件冒泡或者浏览器的默认行为,导致handleMousedown被多次触发,每次触发都会创建一个新的矩形并push到rectangleArray里,短时间内数组就会被塞满大量矩形,直接拖垮Canvas的渲染性能。
修复方式:在点击按钮的逻辑中,添加event.preventDefault()和event.stopPropagation()阻止默认行为和冒泡,避免重复触发;同时确保只有在rectangleDragged为false时才创建新矩形。
4. HandleMouseup中的逻辑问题
在handleMouseup里,你遍历rectangleArray并修改currentRectangle,然后直接删除符合条件的矩形,这可能会导致数组遍历出错(比如删除元素后索引混乱)。另外,你使用setTimeout来设置dragged = false,这个逻辑没有必要,而且可能导致后续拖拽状态混乱。
修复方式:
- 遍历数组时从后往前遍历,避免删除元素导致的索引问题
- 移除不必要的
setTimeout,直接在鼠标抬起时设置dragged = false
修复后的完整代码片段
Update函数
update(): void { crc2.clearRect(0, 0, width, height); crc2.putImageData(img, 0, 0); if (rectangleArray) { // 修复循环初始化问题 for (let i = 0; i < rectangleArray.length; i++) { rectangleArray[i].draw(); } } if (rectangleDragged == true) { currentRectangle.draw(); } }
HandleMousedown函数
handleMousedown(_client: MouseEvent): void { let isLeftClicked = _client.which === 1; if (!isLeftClicked) return; // 点击按钮创建新矩形的逻辑 if (_client.offsetX > xButton1 && _client.offsetX < xButton1 + wButton1 && _client.offsetY > yButton1 && _client.offsetY < yButton1 + hButton1) { // 避免重复创建矩形 if (rectangleDragged) return; let rectangleDraw = new Draw(_client.offsetX, _client.offsetY); let rectangle = new Rectangle(0, rectangleDraw); currentRectangle = rectangle; rectangleArray.push(rectangle); rectanglePresent = true; rectangleDragged = true; // 先移除旧监听再绑定新的,避免重复绑定 window.removeEventListener("mousemove", handleMousemoveRectangle); window.addEventListener("mousemove", handleMousemoveRectangle); window.removeEventListener("mouseup", handleMouseup); window.addEventListener("mouseup", handleMouseup); // 阻止默认行为和冒泡,避免重复触发 _client.preventDefault(); _client.stopPropagation(); console.log(rectangleClick); } // 拖拽已有矩形的逻辑 for (let rectangle of rectangleArray) { if (rectanglePresent && _client.offsetX > rectangle.position.x - rectangle.w / 2 && _client.offsetX < rectangle.position.x + rectangle.w / 2 && _client.offsetY > rectangle.position.y - rectangle.h / 2 && _client.offsetY < rectangle.position.y + rectangle.h / 2) { // 同样先移除旧监听 window.removeEventListener("mousemove", handleMousemoveRectangle); window.addEventListener("mousemove", handleMousemoveRectangle); window.removeEventListener("mouseup", handleMouseup); window.addEventListener("mouseup", handleMouseup); currentRectangle = rectangle; rectangleDragged = true; _client.preventDefault(); _client.stopPropagation(); } } }
HandleMouseup函数
handleMouseup(_client: MouseEvent): void { dragged = false; // 直接设置,不需要setTimeout rectangleDragged = false; // 重置拖拽状态 // 从后往前遍历数组,避免删除元素导致的索引混乱 for (let i = rectangleArray.length - 1; i >= 0; i--) { const rectangle = rectangleArray[i]; if (rectanglePresent && rectangle.position.y > 600 ) { rectangleArray.splice(i, 1); } } // 移除事件监听 window.removeEventListener("mousemove", handleMousemoveRectangle); window.removeEventListener("mouseup", handleMouseup); }
HandleMousemoveRectangle函数
export function handleMousemoveRectangle(_client: MouseEvent): void { currentRectangle.position.x = _client.offsetX; currentRectangle.position.y = _client.offsetY; // 不需要在这里调用draw(),因为update函数会统一处理渲染 rectangleDragged = true; dragged = true; }
这些修改应该能解决你遇到的“大量生成矩形”和“操作停止”的问题:
- 修复了事件绑定的错误,避免重复触发事件处理函数
- 修复了循环初始化问题,确保已有矩形能正常渲染
- 阻止了事件冒泡和默认行为,避免重复创建矩形
- 优化了数组遍历删除的逻辑,避免索引混乱
- 把渲染逻辑统一交给
update函数,避免重复绘制
内容的提问来源于stack exchange,提问作者eugiB

