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

Angular9中HTML Canvas绘制线条与矩形无法同时显示问题

Angular Canvas绘制内容被清空问题修复方案

问题根因

  • 每次点击绘制按钮切换Line/Pencil/Rectangle模式时,都会重新调用createLine()方法,方法内部每次都会重新为canvas的width、height属性赋值:根据Canvas原生特性,只要修改这两个属性,画布就会自动清空所有已绘制内容
  • 多次调用createLine()会重复给canvas绑定鼠标监听事件,导致同一事件触发多次绘制逻辑,出现异常
  • 绘制方法存在语法错误:铅笔绘制方法误用了不存在的e.X属性,线条绘制方法错误给fillRect方法赋值颜色

修复方案

1. 修改HTML代码

给canvas添加模板引用变量,匹配TS中@ViewChild的选择器:

<div class="field">
    <!-- 新增#canvas模板引用 -->
    <canvas #canvas id="canvas"></canvas>
    <div class="tools" style="margin-top: 20px;">
      <button type="button" (click)="createLines()">Line</button>
      <button type="button" (click)="createPencil()">Pencil</button>
      <button type="button" (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>

2. 修正TS代码

将画布初始化、事件绑定逻辑抽离到ngAfterViewInit生命周期中仅执行一次,切换绘制模式时仅修改mode参数,不再重新初始化画布:

import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  // 替换为你的实际组件配置
  selector: 'app-canvas-draw',
  templateUrl: './canvas-draw.component.html',
  styleUrls: ['./canvas-draw.component.css']
})
export class CanvasDrawComponent implements AfterViewInit {
  @ViewChild('canvas', {static: false}) canvas: ElementRef<HTMLCanvasElement>;
  mode: string;
  draw_color: string = 'black';
  snapshot: ImageData;
  private canvasEle: HTMLCanvasElement;
  private context: CanvasRenderingContext2D;
  private startPosition = {x: 0, y: 0};
  private lineCoordinates = {x: 0, y: 0};
  private isDrawStart = false;

  ngAfterViewInit(): void {
    // 初始化逻辑仅执行一次,避免重复清空画布
    this.canvasEle = this.canvas.nativeElement;
    this.context = this.canvasEle.getContext('2d');
    this.canvasEle.height = window.innerHeight - 500;
    this.canvasEle.width = window.innerWidth - 500;
    this.bindEvents();
  }

  createPencil(){
    this.mode = "Pencil";
  }
  createLines(){
    this.mode = "Line";
  }
  createRectangle(){
    this.mode = "Rectangle";
  }

  private bindEvents() {
    this.canvasEle.addEventListener('mousedown', this.mouseDownListener.bind(this));
    this.canvasEle.addEventListener('mousemove', this.mouseMoveListener.bind(this));
    this.canvasEle.addEventListener('mouseup', this.mouseupListener.bind(this));
  }

  private getClientOffset = (event) => {
    const {pageX, pageY} = event.touches ? event.touches[0] : event;
    const x = pageX - this.canvasEle.offsetLeft;
    const y = pageY - this.canvasEle.offsetTop;
    return {x, y} 
  }

  private drawRectangle = (e) => {
    this.context.beginPath();
    this.context.lineWidth = 2;
    // 修正矩形宽高计算,支持反向绘制
    const rectWidth = this.lineCoordinates.x - this.startPosition.x;
    const rectHeight = this.lineCoordinates.y - this.startPosition.y;
    this.context.rect(this.startPosition.x, this.startPosition.y, rectWidth, rectHeight);
    this.context.strokeStyle = this.draw_color;
    this.context.stroke();
    e.preventDefault();
  }

  private drawPencil = (e) => {
    this.context.lineWidth = 2;
    this.context.lineCap = "round";
    this.context.strokeStyle = this.draw_color;
    this.context.beginPath();
    this.context.moveTo(this.startPosition.x, this.startPosition.y);
    this.context.lineTo(this.lineCoordinates.x, this.lineCoordinates.y);
    this.context.stroke();
    // 修正铅笔连续绘制的坐标衔接问题
    this.startPosition = {...this.lineCoordinates};
    e.preventDefault();
  }

  private drawLine = (e) => {
    this.context.beginPath();
    this.context.lineWidth = 2;
    this.context.moveTo(this.startPosition.x, this.startPosition.y);
    this.context.lineTo(this.lineCoordinates.x, this.lineCoordinates.y);
    // 修正颜色赋值错误
    this.context.strokeStyle = this.draw_color;
    this.context.stroke();
    e.preventDefault();
  }

  private mouseDownListener = (event) => {
    this.startPosition = this.getClientOffset(event);
    this.isDrawStart = true;
    this.takesnapshot();
    event.preventDefault();
  }

  private mouseMoveListener = (event) => {
    if(!this.isDrawStart) return;
    this.lineCoordinates = this.getClientOffset(event);
    this.restoresnapshot();
    if(this.mode == 'Line'){
      this.drawLine(event);
    }else if(this.mode == 'Rectangle'){
      this.drawRectangle(event);
    }else{
      this.drawPencil(event);
    }
  }

  private mouseupListener = (event) => {
    this.isDrawStart = false;
    event.preventDefault();
  }

  private takesnapshot = () => {
    this.snapshot = this.context.getImageData(0, 0, this.canvasEle.width, this.canvasEle.height);
  }

  private 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 14:09:01