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

Canvas绘图应用异常:首次绘制正常,后续矩形出现对角线问题

问题分析与解决方案

嘿,我帮你找到了问题的根源!你的代码里存在全局变量和类实例属性不一致的问题,导致切换绘图类型后,实际执行的绘制逻辑和你选择的类型不匹配,从而出现矩形上的对角线。

具体问题点:

  1. 你定义了全局变量type、startX、startY、isDown、storedLines,但在CanvasState类的setType方法中,只更新了实例的this.type,没有同步更新全局的type变量。
  2. 鼠标事件监听(mousedown/mousemove/mouseup)里使用的是全局的type,而不是实例的this.type。这就导致当你切换到矩形模式时,全局type还是初始的line,mousemove事件会同时执行直线和矩形的绘制代码——矩形边框加上一条从起点到当前鼠标的直线,看起来就像是矩形被画了对角线!
  3. 全局变量的使用还会导致代码封装性差,容易出现其他潜在的状态冲突。

修复方案:

把所有相关的状态变量移到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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:56:24