Canvas切换绘图模式后绘制内容被图片遮挡且出现重复图形问题
Angular Canvas绘图功能问题及解决方案
问题描述
- 在Canvas底图上绘制线条后,点击矩形绘制按钮切换绘图模式,此前绘制的内容会被置于底图下方被遮挡。
- 在Canvas上绘制线条、矩形时,会生成多个重复的对应图形。

原有代码
HTML代码
<div class="container"> <div class="row"> <canvas id="canvasImage"></canvas> </div> <div class="tools" style="margin-top: 20px;"> <button type="button" class="btn " (click)="createLines()">Line</button> <button type="button" class="btn " (click)="createPencil()">Pencil</button> <button type="button" class="btn " (click)="createRectangle()">Rectangle</button> <div (click)="change_color_red()" class="color-field" style="background-color: red;"></div> <div (click)="change_color_blue()" class="color-field" style="background-color: blue;"></div> <div (click)="change_color_green()" class="color-field" style="background-color: green;"></div> </div> </div>
Angular TypeScript代码
this.canvasEle = <HTMLCanvasElement> document.getElementById('canvasImage'); this.context = this.canvasEle.getContext("2d"); this.canvasEle.width = window.innerWidth-500; this.canvasEle.height = window.innerHeight -200; createPencil(){ this.mode = "Pencil"; this.drawOnCanvas(); } createLines(){ this.mode = "Line"; this.drawOnCanvas(); } createRectangle(){ this.mode = "Rectangle"; this.drawOnCanvas(); } drawOnCanvas(){ var offsetX,offsetY; this.img = new Image(); this.img.width = 800; this.img.height = 600; this.img.onload = () => { // this.context.drawImage(this.img,0,0,this.img.width, this.img.height, // 0,0, this.canvasEle.width,this.canvasEle.height); loadImage(); }; this.img.src = this.imageList['0'].LINK; let startPosition = {x: 0, y: 0}; let lineCoordinates = {x: 0, y: 0}; let isDrawStart = false; this.draw_color = "black"; const loadImage = () => { this.context.drawImage(this.img,10,10,this.img.width, this.img.height, 20,20, this.canvasEle.width,this.canvasEle.height); } const getClientOffset = (event) => { const {pageX, pageY} = event.touches ? event.touches[0] : event; var BB= this.canvasEle.getBoundingClientRect(); const x = pageX - BB.left; const y = pageY - BB.top; return { x, y } } const reOffset = () => { var BB= this.canvasEle.getBoundingClientRect(); offsetX=BB.left; offsetY=BB.top; } const drawRectangle = (e) => { this.context.beginPath(); this.context.lineWidth=2; this.context.rect(startPosition.x, startPosition.y,lineCoordinates.x, lineCoordinates.y); this.context.strokeStyle = this.draw_color; this.context.stroke(); e.preventDefault(); } const drawPencil = (e) => { this.context.lineWidth = 2; this.context.moveTo(e.X,e.Y); this.context.lineTo(e.clientX- offsetX, e.clientY- offsetY); this.context.strokeStyle = this.draw_color; this.context.lineCap = "round"; this.context.lineJoin = "round"; this.context.stroke(); e.preventDefault(); } const drawLine = (e) => { this.context.beginPath(); this.context.lineWidth=2; this.context.moveTo(startPosition.x, startPosition.y); this.context.lineTo(lineCoordinates.x, lineCoordinates.y); this.context.strokeStyle=this.draw_color; this.context.stroke(); this.context.closePath(); e.preventDefault(); } const mouseDownListener = (event) => { startPosition = getClientOffset(event); isDrawStart = true; //takesnapshot(); event.preventDefault(); } const mouseMoveListener = (event) => { if(!isDrawStart) return; if(this.mode == 'Line'){ //restoresnapshot(); reOffset(); lineCoordinates = getClientOffset(event); drawLine(event); }else if(this.mode == 'Rectangle'){ //restoresnapshot(); lineCoordinates = getClientOffset(event); drawRectangle(event); }else{ //restoresnapshot(); // lineCoordinates = getClientOffset(event); drawPencil(event); } } const mouseupListener = (event) => { isDrawStart = false; this.context.beginPath(); event.preventDefault(); } const takesnapshot = () => { this.snapshot = this.context.getImageData(0, 0, this.canvasEle.width, this.canvasEle.height); } const restoresnapshot = () => { this.context.putImageData(this.snapshot,0, 0); } this.canvasEle.addEventListener('mousedown', mouseDownListener); this.canvasEle.addEventListener('mousemove', mouseMoveListener); this.canvasEle.addEventListener('mouseup', mouseupListener); } change_color_red(){ this.draw_color = "red"; } change_color_blue(){ this.draw_color = "blue"; } change_color_green(){ this.draw_color = "green"; }
问题原因&修复方案
问题1:切换模式后原有绘制内容被底图遮挡
原因:每次切换模式调用drawOnCanvas时,都会重新加载底图并调用drawImage把底图画在最上层,直接覆盖了之前绘制的所有图形。
修复:
- 底图加载逻辑单独抽离,只在组件初始化的时候执行一次,不要放在
drawOnCanvas里重复执行。 - 如果业务需要动态更新底图,更新前先保存当前画布的完整快照,底图绘制完成后再把快照绘制到底图上方即可。
问题2:绘制线条、矩形时出现多个重复图形
原因:
- 你注释掉了绘制过程中的快照恢复逻辑,每次鼠标移动绘制新的预览图形时,没有清空上一次鼠标移动时绘制的旧预览图形,导致所有移动过程中的图形都留在了画布上。
- 每次调用
drawOnCanvas切换模式时,都会给canvas重复绑定相同的鼠标事件监听器,切换几次模式就绑定了几个监听器,一次鼠标操作会触发多次绘制逻辑,自然就出现重复图形。
修复:
- 组件初始化时只绑定一次鼠标事件监听器,
drawOnCanvas仅负责修改绘制模式变量,不要重复绑定事件。 - 启用注释的快照逻辑:鼠标按下时保存当前画布快照,每次鼠标移动时先恢复快照再绘制当前预览图形,避免拖动过程中生成多个重复的预览图形。
- 修复
drawPencil里不存在的e.X属性,换成统一的偏移坐标计算逻辑,同时调整矩形宽高计算,避免反向拖动时绘制异常。
修复后核心代码示例
// 组件初始化时执行,仅执行一次 ngOnInit() { this.canvasEle = <HTMLCanvasElement> document.getElementById('canvasImage'); this.context = this.canvasEle.getContext("2d"); this.canvasEle.width = window.innerWidth-500; this.canvasEle.height = window.innerHeight -200; this.draw_color = "black"; // 只加载一次底图 this.loadBaseImage(); // 只绑定一次事件 this.bindCanvasEvents(); } // 底图加载逻辑单独抽离 loadBaseImage() { this.img = new Image(); this.img.width = 800; this.img.height = 600; this.img.onload = () => { this.context.drawImage(this.img,10,10,this.img.width, this.img.height, 20,20, this.canvasEle.width,this.canvasEle.height); // 加载完底图保存初始快照 this.takesnapshot(); }; this.img.src = this.imageList['0'].LINK; } // 事件绑定仅执行一次 bindCanvasEvents() { let startPosition = {x: 0, y: 0}; let lineCoordinates = {x: 0, y: 0}; let isDrawStart = false; let offsetX,offsetY; const getClientOffset = (event) => { const {pageX, pageY} = event.touches ? event.touches[0] : event; const BB= this.canvasEle.getBoundingClientRect(); return { x: pageX - BB.left, y: pageY - BB.top } } const reOffset = () => { const BB= this.canvasEle.getBoundingClientRect(); offsetX=BB.left; offsetY=BB.top; } const drawRectangle = (e) => { this.context.beginPath(); this.context.lineWidth=2; this.context.rect(startPosition.x, startPosition.y,lineCoordinates.x - startPosition.x, lineCoordinates.y - startPosition.y); this.context.strokeStyle = this.draw_color; this.context.stroke(); e.preventDefault(); } const drawPencil = (e) => { const coord = getClientOffset(e); this.context.lineWidth = 2; this.context.moveTo(lineCoordinates.x, lineCoordinates.y); this.context.lineTo(coord.x, coord.y); this.context.strokeStyle = this.draw_color; this.context.lineCap = "round"; this.context.lineJoin = "round"; this.context.stroke(); lineCoordinates = coord; e.preventDefault(); } const drawLine = (e) => { this.context.beginPath(); this.context.lineWidth=2; this.context.moveTo(startPosition.x, startPosition.y); this.context.lineTo(lineCoordinates.x, lineCoordinates.y); this.context.strokeStyle=this.draw_color; this.context.stroke(); this.context.closePath(); e.preventDefault(); } const mouseDownListener = (event) => { startPosition = getClientOffset(event); lineCoordinates = startPosition; isDrawStart = true; this.takesnapshot(); event.preventDefault(); } const mouseMoveListener = (event) => { if(!isDrawStart) return; this.restoresnapshot(); if(this.mode == 'Line'){ reOffset(); lineCoordinates = getClientOffset(event); drawLine(event); }else if(this.mode == 'Rectangle'){ lineCoordinates = getClientOffset(event); drawRectangle(event); }else{ drawPencil(event); } } const mouseupListener = (event) => { isDrawStart = false; this.context.beginPath(); this.takesnapshot(); event.preventDefault(); } this.canvasEle.addEventListener('mousedown', mouseDownListener); this.canvasEle.addEventListener('mousemove', mouseMoveListener); this.canvasEle.addEventListener('mouseup', mouseupListener); } // 模式切换仅修改mode变量 createPencil(){ this.mode = "Pencil"; } createLines(){ this.mode = "Line"; } createRectangle(){ this.mode = "Rectangle"; } takesnapshot = () => { this.snapshot = this.context.getImageData(0, 0, this.canvasEle.width, this.canvasEle.height); } restoresnapshot = () => { this.context.putImageData(this.snapshot,0, 0); } change_color_red(){ this.draw_color = "red"; } change_color_blue(){ this.draw_color = "blue"; } change_color_green(){ this.draw_color = "green"; }
内容的提问来源于stack exchange,提问作者Mritunjay Sharma
相关产品推荐
相关产品推荐

