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

JavaScript实现Etch-A-Sketch鼠标按住绘图时mouseup不触发的问题

问题描述

我在做The Odin Project的Etch-A-Sketch项目,要实现按住鼠标时在自定义pixel div上绘制灰色像素的功能。因为没有mousehold事件,我用mousedown把布尔值设为true,鼠标划过pixel且布尔值为true时,像素变灰色。

但现在有个bug:有时候mouseup事件触发不了,导致布尔值没法重置,这时拖动鼠标会一直绘图,而且这个bug没有固定触发规律。想知道有没有更好的绘图方式,当前的mousedown/mouseup用法是否正确,或者有没有其他合适的事件可用。

问题截图

解决方案

核心问题原因

mouseup事件没触发,大概率是因为鼠标按下后移出了canvas元素范围——此时mouseup事件会触发在document上,而不是canvas,导致canvas绑定的mouseup监听器接收不到事件,down变量一直保持true状态。

改进方案

  1. 将mouseup绑定到document:确保无论鼠标移到页面哪个位置,松开时都能触发事件重置绘制状态
  2. 添加mouseleave监听canvas:鼠标移出画布时自动停止绘制,避免异常
  3. 改用mousemove处理绘制:逻辑更顺畅,同时在mousedown时直接绘制点击的像素,符合操作直觉
  4. 阻止默认行为:避免浏览器默认的拖拽选中行为干扰绘制

修改后的代码

JavaScript 代码

const canvas = document.getElementById("canvas");

// 获取画布尺寸
let canvasWidth = canvas.clientWidth;
let canvasHeight = canvas.clientHeight;

const LOW_RES = 16;
const MID_RES = 32;
const HIGH_RES = 64;
create_pixels(MID_RES);

// 分辨率切换按钮逻辑
const lowResButton = document.getElementById("low");
lowResButton.addEventListener("click", function(){
    deletePixels();
    create_pixels(LOW_RES);
})
const midResButton = document.getElementById("medium");
midResButton.addEventListener("click", function(){
    deletePixels();
    create_pixels(MID_RES);
})
const highResButton = document.getElementById("High");
highResButton.addEventListener("click", function(){
    deletePixels();
    create_pixels(HIGH_RES);
})

// 清空画布按钮逻辑
const clearButton = document.getElementById("clear");
clearButton.addEventListener("click", function (){
    let pixels = Array.from(document.getElementsByClassName("pixel"));
    pixels.forEach(e => {
        e.style.backgroundColor = "white";
    });
})

function deletePixels(){
    let pixels = Array.from(document.getElementsByClassName("pixel"));
    pixels.forEach(element => {
        element.remove();
    });
}

function create_pixels(res) {
    let numberOfVerticalPixels = res;
    // 计算正方形像素的尺寸
    let pixelHeight = Math.floor(canvasHeight / res);
    let numberOfHorizontalPixels = Math.floor(canvasWidth / pixelHeight);
    let pixelWidth = pixelHeight;

    // 调整画布尺寸以适配像素
    let newCanvasWidth = numberOfHorizontalPixels * pixelWidth;
    canvas.style.width = newCanvasWidth + "px";
    let newCanvasHeight = numberOfVerticalPixels * pixelHeight;
    canvas.style.height = newCanvasHeight + "px";

    let stringStyle = "width:" + pixelWidth + "px; height: " + pixelHeight + "px;";
    // 创建并添加像素到画布
    for (let i = 0; i < numberOfHorizontalPixels * numberOfVerticalPixels; i++) {
        const pixel = document.createElement("div");
        pixel.classList.add("pixel")
        pixel.setAttribute("style", stringStyle);
        canvas.appendChild(pixel);
    }
}

// 改进的绘制逻辑
let isDrawing = false;

// 鼠标按下:开始绘制,点击时直接上色当前像素
canvas.addEventListener("mousedown", (e) => {
    isDrawing = true;
    if (e.target.classList.contains("pixel")) {
        e.target.style.backgroundColor = "grey";
    }
    e.preventDefault(); // 阻止默认拖拽选中行为
});

// 鼠标移动:持续上色
canvas.addEventListener("mousemove", (e) => {
    if (isDrawing && e.target.classList.contains("pixel")) {
        e.target.style.backgroundColor = "grey";
    }
});

// 鼠标松开:停止绘制(绑定到document确保全局触发)
document.addEventListener("mouseup", () => {
    isDrawing = false;
});

// 鼠标移出画布:停止绘制
canvas.addEventListener("mouseleave", () => {
    isDrawing = false;
});

代码说明

  • 移除了原有的mouseover事件,改用mousemove直接处理绘制,逻辑更连贯
  • mouseup绑定到document,彻底解决鼠标移出画布后无法停止绘制的问题
  • 增加mouseleave监听,鼠标离开画布时自动停止,进一步避免异常状态
  • mousedown时直接绘制点击的像素,符合用户点击即上色的直觉操作
  • 添加e.preventDefault()阻止浏览器默认的文本/元素选中行为,避免干扰绘制

内容的提问来源于stack exchange,提问作者Funlamb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 21:25:01