Javascript Graphics 2D绘图问题:拖拽绘矩形时停动即消失
拖拽绘制矩形时鼠标停止移动后矩形消失的问题
我正在开发类Visual Studio风格的Web表单编辑器,核心需求是实现拖拽绘制矩形功能:绘制过程中矩形轮廓跟随鼠标移动,每次鼠标位移时清除之前绘制的矩形,所以绘制新矩形前会恢复鼠标按下(mousedown)时保存的画布快照。
目前功能在鼠标持续移动时表现尚可(虽有闪烁但可接受),但只要鼠标停止移动超过瞬间,矩形就会消失。我试过把矩形绘制代码移入mousemove事件的图片加载回调,但这样直到mouseup前都看不到任何内容,找不到解决思路。
现有代码
JavaScript
var ctx; var startX, startY, endX, endY, lastX, lastY; var rect; var lastCanvas; var isMouseDown = false; function init() { rect = myCanvas.getBoundingClientRect(); ctx = myCanvas.getContext("2d"); ctx.lineWidth = 1; ctx.strokeStyle = "black"; ctx.fillStyle = "wheat"; ctx.fillRect(0, 0, rect.width, rect.height); myCanvas.addEventListener("mousemove",(e)=> { if ( isMouseDown ) { var canvasPic = new Image(); canvasPic.src = lastCanvas; canvasPic.addEventListener("load", (e)=> { ctx.drawImage(canvasPic, 0, 0); }); var x = e.clientX - rect.left; var y = e.clientY - rect.top; ctx.strokeRect(startX, startY, x-startX, y-startY); lastX = x; lastY = y; } }); myCanvas.addEventListener("mousedown",(e)=> { lastCanvas = myCanvas.toDataURL(); startX = e.clientX - rect.left; startY = e.clientY - rect.top; lastX = startX; lastY = startY; isMouseDown = true; }); myCanvas.addEventListener("mouseup",(e)=> { isMouseDown = false; endX = e.clientX - rect.left; endY = e.clientY - rect.top; ctx.strokeRect(startX, startY, endX-startX, endY-startY); }); }
HTML
<!DOCTYPE html> <html> <body onLoad="init();"> <canvas id="myCanvas" width="600" height="400" style="border:1px solid black"></canvas> </body> </html>
问题原因
问题出在mousemove事件里的异步图片加载逻辑:每次移动鼠标都会创建新的Image实例并设置src,而图片加载是异步操作。当鼠标停止移动时,最后一次mousemove触发的图片加载完成后,会把画布恢复到初始快照状态,但此时没有后续的mousemove事件来重新绘制矩形,导致矩形消失。
另外,把绘制代码放进加载回调会看不到实时矩形,是因为每次mousemove都要等图片加载完才绘制,而图片加载有延迟,鼠标移动过程中回调还没执行就触发了下一次mousemove,最终只有最后一次的绘制会在mouseup前完成。
解决方案
不需要用toDataURL保存快照再恢复,直接在mousemove时重新绘制整个画布:先清除画布,再绘制初始背景,最后绘制当前的矩形轮廓。这样既避免了异步加载的问题,也能实时更新矩形。
修改后的JavaScript代码:
var ctx; var startX, startY; var rect; var isMouseDown = false; function init() { rect = myCanvas.getBoundingClientRect(); ctx = myCanvas.getContext("2d"); ctx.lineWidth = 1; ctx.strokeStyle = "black"; ctx.fillStyle = "wheat"; // 绘制初始背景 drawBackground(); myCanvas.addEventListener("mousemove",(e)=> { if (isMouseDown) { // 重新绘制背景(清除之前的矩形) drawBackground(); // 计算当前矩形的坐标和尺寸 var x = e.clientX - rect.left; var y = e.clientY - rect.top; var width = x - startX; var height = y - startY; // 绘制当前矩形轮廓 ctx.strokeRect(startX, startY, width, height); } }); myCanvas.addEventListener("mousedown",(e)=> { startX = e.clientX - rect.left; startY = e.clientY - rect.top; isMouseDown = true; }); myCanvas.addEventListener("mouseup",(e)=> { isMouseDown = false; // 鼠标松开后,重新绘制背景并确认绘制最终矩形 drawBackground(); var x = e.clientX - rect.left; var y = e.clientY - rect.top; var width = x - startX; var height = y - startY; ctx.strokeRect(startX, startY, width, height); }); } // 封装绘制背景的函数,避免重复代码 function drawBackground() { ctx.fillRect(0, 0, rect.width, rect.height); }
优化说明
- 去掉了
lastCanvas和异步图片加载的逻辑,改用同步的画布重绘,彻底解决异步导致的矩形消失问题 - 封装
drawBackground函数,减少代码重复 - 鼠标松开时重新绘制背景再画最终矩形,确保画布状态正确
内容的提问来源于stack exchange,提问作者Chris B
相关产品推荐
相关产品推荐

