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

Angular中Canvas仅绘制最后一次迭代:棋盘实现故障求助

解决Angular中Canvas绘制棋盘仅显示最后一个方块的问题

兄弟,我太懂这个坑了!这是前端开发里超常见的异步闭包陷阱,当年我刚摸Canvas的时候也踩过一模一样的雷。问题出在你循环里的img.onload回调上——它是异步执行的,当所有图片的onload触发时,外层循环早就跑完了,posx和posy已经变成最后一轮循环的数值,所以所有drawImage都会把图片画在同一个最终位置,自然就只看到最后一个方块。

给你两种靠谱的解决方案,按需选:

方案一:预加载图片后同步绘制(推荐)

这种方法不仅能解决异步问题,还能避免重复创建Image对象、重复请求资源,性能更优:

createBoard(): void { 
  const row = 8; 
  const col = 8; 
  let x = 1; 
  let y = 'a'; 
  const ctx = this.canvas.nativeElement.getContext('2d'); 

  // 预加载棋盘的两种方块图片
  const lightSquare = new Image();
  const darkSquare = new Image();
  let loadedImageCount = 0;
  const totalImages = 2;

  // 所有图片加载完成后再开始绘制棋盘
  const startDrawing = () => {
    loadedImageCount++;
    if (loadedImageCount === totalImages) {
      this.drawChessboard(ctx, lightSquare, darkSquare);
    }
  };

  lightSquare.onload = startDrawing;
  darkSquare.onload = startDrawing;
  lightSquare.src = "assets/img/Boardsquare_light_normal.png";
  darkSquare.src = "assets/img/Boardsquare_dark_normal.png";
}

// 抽离绘制逻辑,代码更清晰
private drawChessboard(ctx: CanvasRenderingContext2D, lightImg: HTMLImageElement, darkImg: HTMLImageElement): void {
  let posy = 0;
  let x = 1;
  let y = 'a';

  for(let i = 0; i < 8; i++){ 
    let posx = 0;
    for(let j = 0; j < 8; j++) { 
      // 根据行列和判断使用亮/暗方块
      const currentImg = (i + j) % 2 === 0 ? lightImg : darkImg;
      // 图片已加载完成,直接同步绘制
      ctx.drawImage(currentImg, posx, posy, 75, 75); 
      
      y = this.nextChar(y); 
      posx += 75; 
    } 
    x += 1; 
    y = 'a'; 
    posy += 75; 
  }
}

方案二:捕获循环变量的快照(快速修复)

如果不想大改代码,只需要在循环里把当前的posx和posy存成独立变量,让每个onload回调引用自己的变量快照就行:

createBoard(): void { 
  let row = 8; 
  let col = 8; 
  let x = 1; 
  let y = 'a'; 
  let posx = 0; 
  let posy = 0; 
  let ctx = this.canvas.nativeElement.getContext('2d'); 

  for(let i = 0; i < row; i++){ 
    posx = 0; 
    for(let j = 0; j < col; j++) { 
      // 关键:用const保存当前循环的位置快照
      const currentPosX = posx;
      const currentPosY = posy;
      
      let img = new Image(); 
      img.onload = () => { 
        // 这里引用的是当前循环的快照变量,不是外层会变化的posx/posy
        ctx.drawImage(img, currentPosX, currentPosY, 75, 75); 
      } 
      
      if ((i + j) % 2 == 0) { 
        img.src = "assets/img/Boardsquare_light_normal.png"; 
      } else { 
        img.src = "assets/img/Boardsquare_dark_normal.png"; 
      } 
      
      y = this.nextChar(y); 
      posx += 75; 
    } 
    x += 1; 
    y = 'a'; 
    posy += 75; 
  } 
}

为什么第二种方案有效?

因为const currentPosX和currentPosY是在每次内层循环里创建的新变量,每个img.onload箭头函数会捕获自己所在循环迭代的这两个变量,不会被后续循环的数值覆盖。

内容的提问来源于stack exchange,提问作者senna terpstra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.01 01:27:34