如何在JavaScript中清除鼠标坐标处的矩形且不影响其他图形?
解决方案
你的代码核心问题有两个:一是把mousemove事件监听放在了渲染循环loop里,每次循环都会新增一个监听,导致鼠标移动时重复绘制矩形;二是鼠标矩形的绘制脱离了统一的渲染流程,和网格的绘制不同步,所以清屏时要么全清要么无法单独处理旧的鼠标矩形。
下面是修复后的完整代码,实现鼠标跟随矩形且不影响网格:
<!DOCTYPE html> <html> <style> html, body { height: 100%; margin: 0; } body { display: flex; align-items: center; justify-content: center; } canvas { border: 1px solid orange; background: black; } </style> <head> <base target="_top"> </head> <body> <canvas width="1250" height="600" id="game"></canvas> <script> const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); // 存储鼠标位置,初始设到画布外避免初始绘制 let mousePos = { x: -100, y: -100 }; // 网格参数提前定义,不用每次循环重复声明 const levelMax = 5; const gridBoxSize = 75; // 鼠标事件监听放外面,只绑定一次 window.addEventListener("mousemove", (e)=>{ mousePos.x = e.offsetX; mousePos.y = e.offsetY; }); function loop(){ // 每次循环先全清画布,统一重绘所有元素 ctx.clearRect(0, 0, canvas.width, canvas.height); // 绘制网格盒子 ctx.fillStyle = "lime"; for (let j = 0; j < levelMax; j++) { for (let i = 0; i < levelMax; i++) { const x = gridBoxSize * i; const y = 525 - (j * gridBoxSize); ctx.fillRect(x, y, gridBoxSize, gridBoxSize); } } // 绘制鼠标跟随的矩形(换个颜色区分网格) ctx.fillStyle = "white"; ctx.fillRect(mousePos.x, mousePos.y, 50, 50); requestAnimationFrame(loop); } loop(); </script> </body> </html>
关键修改说明:
- 把
mousemove监听移到loop外面,避免重复绑定事件导致多次绘制。 - 用
mousePos变量存储鼠标坐标,在loop里统一绘制鼠标矩形,这样每次清屏后会重新画所有元素,旧的鼠标矩形自然被清除,新的随鼠标位置更新。 - 删掉了没用的
box数组,直接计算网格坐标绘制,简化代码。
如果担心全清画布的性能(比如网格元素很多),可以只清除上一次的鼠标矩形,再绘制新的,但这种方式要注意修复被清除的网格部分:
// 新增变量存储上一次鼠标位置 let lastMousePos = { x: -100, y: -100 }; const mouseBoxSize = 50; window.addEventListener("mousemove", (e)=>{ // 清除上一次的鼠标矩形 ctx.clearRect(lastMousePos.x, lastMousePos.y, mouseBoxSize, mouseBoxSize); // 修复被清除的网格部分(重新绘制对应区域的网格) redrawGridArea(lastMousePos.x, lastMousePos.y, mouseBoxSize); // 更新当前鼠标位置并绘制新矩形 mousePos.x = e.offsetX; mousePos.y = e.offsetY; ctx.fillStyle = "white"; ctx.fillRect(mousePos.x, mousePos.y, mouseBoxSize, mouseBoxSize); // 记录当前位置为下一次清除用 lastMousePos = { ...mousePos }; }); // 重新绘制指定区域内的网格盒子 function redrawGridArea(clearX, clearY, clearSize) { ctx.fillStyle = "lime"; for (let j = 0; j < levelMax; j++) { for (let i = 0; i < levelMax; i++) { const gridX = gridBoxSize * i; const gridY = 525 - (j * gridBoxSize); // 检查网格是否和清除区域重叠 if (gridX < clearX + clearSize && gridX + gridBoxSize > clearX && gridY < clearY + clearSize && gridY + gridBoxSize > clearY) { ctx.fillRect(gridX, gridY, gridBoxSize, gridBoxSize); } } } } function loop(){ // 初始化绘制一次网格 ctx.fillStyle = "lime"; for (let j = 0; j < levelMax; j++) { for (let i = 0; i < levelMax; i++) { const x = gridBoxSize * i; const y = 525 - (j * gridBoxSize); ctx.fillRect(x, y, gridBoxSize, gridBoxSize); } } requestAnimationFrame(loop); } loop();
这种方式适合网格固定的场景,但代码复杂度更高,一般推荐全清画布统一重绘的方式,逻辑更简单不易出错。
内容的提问来源于stack exchange,提问作者Andrew Warby
相关产品推荐
相关产品推荐

