You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 23:22:09