Canvas绘图应用异常:首次绘制正常,后续矩形出现对角线问题
问题分析与解决方案
嘿,我帮你找到了问题的根源!你的代码里存在全局变量和类实例属性不一致的问题,导致切换绘图类型后,实际执行的绘制逻辑和你选择的类型不匹配,从而出现矩形上的对角线。
具体问题点:
- 你定义了全局变量
type、startX、startY、isDown、storedLines,但在CanvasState类的setType方法中,只更新了实例的this.type,没有同步更新全局的type变量。 - 鼠标事件监听(mousedown/mousemove/mouseup)里使用的是全局的
type,而不是实例的this.type。这就导致当你切换到矩形模式时,全局type还是初始的line,mousemove事件会同时执行直线和矩形的绘制代码——矩形边框加上一条从起点到当前鼠标的直线,看起来就像是矩形被画了对角线! - 全局变量的使用还会导致代码封装性差,容易出现其他潜在的状态冲突。
修复方案:
把所有相关的状态变量移到CanvasState类的实例属性中,确保所有逻辑都使用实例的属性,避免全局变量的干扰。
修改后的完整代码:
class CanvasState{ // **** First some setup! **** constructor(canvas){ this.canvas = canvas; this.type = "line"; // 初始类型设为line,不再用全局变量 this.width = canvas.width; this.height = canvas.height; this.ctx = canvas.getContext('2d'); this.offsetX = canvas.offsetLeft; this.offsetY = canvas.offsetTop; this.valid = false; // when set to false, the canvas will redraw everything this.shapes = []; // the collection of things to be drawn this.dragging = false; // Keep track of when we are dragging this.selection = null; this.dragoffx = 0; // See mousedown and mousemove events for explanation this.dragoffy = 0; this.selectionColor = '#CC0000'; this.selectionWidth = 2; // 实例状态变量,替代全局变量 this.storedLines = []; this.startX = 0; this.startY = 0; this.isDown = false; var myState = this; canvas.addEventListener('selectstart', function(e) { e.preventDefault(); return false; }, false); // Up, down, and move are for dragging canvas.addEventListener('mousedown', function(e) { e.preventDefault(); e.stopPropagation(); canvas.style.cursor = "crosshair"; // 实时获取offset,避免位置偏差 const offsetX = canvas.offsetLeft; const offsetY = canvas.offsetTop; const mouseX = parseInt(e.clientX - offsetX); const mouseY = parseInt(e.clientY - offsetY); myState.isDown = true; myState.startX = mouseX; myState.startY = mouseY; }, true); canvas.addEventListener('mousemove', function(e) { e.preventDefault(); e.stopPropagation(); const ctx = myState.ctx; if (!myState.isDown) return; // 重绘已保存的图形 myState.redrawStoredLines(); // 实时获取offset const offsetX = canvas.offsetLeft; const offsetY = canvas.offsetTop; const mouseX = parseInt(e.clientX - offsetX); const mouseY = parseInt(e.clientY - offsetY); if(myState.type === "rect"){ ctx.beginPath(); ctx.rect(myState.startX, myState.startY, mouseX - myState.startX, mouseY - myState.startY); ctx.stroke(); } if(myState.type === "line"){ ctx.beginPath(); ctx.moveTo(myState.startX, myState.startY); ctx.lineTo(mouseX, mouseY); ctx.stroke(); } }, true); canvas.addEventListener('mouseup', function(e) { canvas.style.cursor = "default"; e.preventDefault(); e.stopPropagation(); myState.isDown = false; const offsetX = canvas.offsetLeft; const offsetY = canvas.offsetTop; const mouseX = parseInt(e.clientX - offsetX); const mouseY = parseInt(e.clientY - offsetY); // 保存当前绘制的图形到实例的storedLines myState.storedLines.push({ type: myState.type, x1: myState.startX, y1: myState.startY, x2: mouseX, y2: mouseY }); console.log(myState.storedLines); myState.redrawStoredLines(); }, true); canvas.addEventListener('mouseout', function(e) { // 注意:原代码是handleMouseOut,应该是mouseout事件 e.preventDefault(); e.stopPropagation(); if (!myState.isDown) return; myState.isDown = false; const offsetX = canvas.offsetLeft; const offsetY = canvas.offsetTop; const mouseX = parseInt(e.clientX - offsetX); const mouseY = parseInt(e.clientY - offsetY); myState.storedLines.push({ type: myState.type, x1: myState.startX, y1: myState.startY, x2: mouseX, y2: mouseY }); myState.redrawStoredLines(); }, true); } setType(newtype){ if ( newtype === 'line' ) { this.type = "line"; } if ( newtype === 'rect' ) { this.type = "rect"; console.log('settype:' + this.type); } } redrawStoredLines() { const ctx = this.ctx; ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); if (this.storedLines.length === 0) return; // redraw each stored line for (let i = 0; i < this.storedLines.length; i++) { const shape = this.storedLines[i]; if(shape.type === "line"){ ctx.beginPath(); ctx.moveTo(shape.x1, shape.y1); ctx.lineTo(shape.x2, shape.y2); ctx.stroke(); } if(shape.type === "rect"){ ctx.beginPath(); ctx.rect(shape.x1, shape.y1, shape.x2 - shape.x1, shape.y2 - shape.y1); ctx.stroke(); } } } } // 绑定单选按钮事件 const radios = document.querySelectorAll('input[type=radio][name="shape"]'); function changeHandler(event) { console.log(event.target.value); mycanvas.setType(event.target.value); } radios.forEach(radio => { radio.addEventListener('change', changeHandler); }); // 初始化Canvas实例 const mycanvas = new CanvasState(document.getElementById('mainCanvas'));
body{ font-family: Arial, Helvetica, sans-serif; font-size: 16px; /* 原代码是font-weight,应该是font-size */ } .container{ margin: 10px; padding: 2px; border: solid 1px black; background-color: #505050 } h3{ margin: 10px; padding: 2px; } .toolbox{ display: inline; list-style-type: none; } .toolbox > li{ display: inline-block; margin: 0; vertical-align: middle; } div.sliders div{ /*margin-top: 20px; */ display: inline-block; } .in-line p{ font-size: 14px; display: block; } .in-line input{ margin: 5px; display: block; } #square{ margin-top: 20px; margin-left: 48%; width: 50px; height: 50px; background-color: rgb(127, 127, 127); } #rgbcolor{ font-size: 14px; text-align: center; } #clearbtn{ width: 50px; margin-left: 48%; margin-top: 10px; } .canvas{ margin: 10px; position: relative; width: 600px; height: 400px; border: solid 1px black; background-color: #ffffff; cursor: crosshair; } #coords{ text-align: center; }
<div class="container"> <h3>Drawing Shapes</h3> <div> <ul class="toolbox"> <li id="btns"> <div> <input type="radio" name="shape" id="line" value="line" checked>Line<br> <input type="radio" name="shape" id="rect" value="rect">Rectangle<br> </div> </li> <!-- 原代码缺少这个闭合标签 --> <li id="cnvs"> <div> <canvas id="mainCanvas" class="canvas" width="600" height="400"></canvas> <h3 id="coords">(X, Y) : (0 , 0)</h3> </div> </li> </ul> </div> </div>
额外修复的小问题:
- 原CSS里的
font-weight: 16px是错误的,改为font-size: 16px - 原HTML里的
<li id="btns">没有闭合标签,补充完整 - 原事件监听里的
handleMouseOut是错误的事件名,改为标准的mouseout - 给初始的line单选按钮加上
checked属性,默认选中 - 使用
let/const替代var,提升代码规范性
这样修改后,所有状态都由CanvasState实例管理,绘图类型切换后逻辑一致,就不会再出现矩形带对角线的问题啦!
内容的提问来源于stack exchange,提问作者sylvain
相关产品推荐
相关产品推荐

