如何使OffscreenCanvas绘制的内容同步到多个Canvas中
===================================

#### 问题现象

父子自定义组件中都存在Canvas画布，如何能够绘制一次内容使其同步在这些Canvas画布中？

#### 效果预览

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/c6/v3/_lI9ezk6S92ruVfctqOBbw/zh-cn_image_0000002628393364.png?HW-CC-KV=V1&HW-CC-Date=20260709T134303Z&HW-CC-Expire=86400&HW-CC-Sign=CAA984EC2C1A1BBF06DD4FB8F78C60F2248783149CABFAE318DBD65190D96E0B "点击放大")

#### 背景知识

*   [Canvas](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-components-canvas-canvas)是画布组件，提供了一个用于绘制内容的区域。
*   [OffscreenCanvas](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-components-offscreencanvas)是用于离屏绘制的画笔，可以在一个类中创建并绘制内容，然后在自定义组件中通过[getImageData](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-offscreencanvasrenderingcontext2d#getimagedata)将绘制的内容传递到不同Canvas上。
*   [使用状态变量驱动画布刷新](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-drawing-customization-on-canvas#使用状态变量驱动画布刷新)：状态变量变更时，本身不会导致Canvas中绘制内容的更新，这时可以用@Watch去监听状态变量的更新，通过@Watch的回调方法去主动地更新绘制内容。

#### 解决方案

1.  创建一个类，包含OffscreenCanvas的初始化及绘制方法。该类通过@Observed装饰，使得绘制的内容可以传递到不同组件中的Canvas上。
    
    import { LengthMetricsUnit } from '@kit.ArkUI';
    
    const WH: number = 256;
    
    @Observed
    class DemoLayer {
      canvas: OffscreenCanvas;
      context: OffscreenCanvasRenderingContext2D;
      data: ImageData | undefined;
      fp: number = 0;
    
      constructor() {
        this.canvas = new OffscreenCanvas(WH, WH, LengthMetricsUnit.PX);
        this.context = this.canvas.getContext('2d', new RenderingContextSettings(false));
      }
    
      draw(): void {
        this.context.fillStyle = '#5291FF';
        this.context.fillRect(0, 0, WH, WH);
        this.context.lineWidth = 5;
        this.context.strokeStyle = '#F1F3F5';
        this.context.beginPath();
        this.context.ellipse(WH / 2, WH / 2, WH / 2, WH / 2, 0, 0, Math.PI \* 2);
        this.context.stroke();
      }
    
      flip(): void {
        this.fp = 1 - this.fp;
      }
    }
    
2.  通过类的实例，将绘制的内容传递给不同Canvas的画笔。
    
    ![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/ad/v3/8EKrVIGAS0uX3ZyzJTADJA/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T134303Z&HW-CC-Expire=86400&HW-CC-Sign=BEFE4513FF7295E291C2C108E8157E33AF6026EB136F6B694703F5E2347CE8F4)
    
    在Index中是直接通过点击操作执行的putImageData，在DemoItem中则是通过@Watch的回调执行的putImageData。
    
    @Entry
    @Component
    struct SyncCanvasContent {
      message: string = '点击最大的方框进行绘图';
      private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(new RenderingContextSettings(false));
      private context2: CanvasRenderingContext2D = new CanvasRenderingContext2D(new RenderingContextSettings(false));
      @State layer: DemoLayer = new DemoLayer();
    
      build() {
        RelativeContainer() {
    
          Text(this.message)
            .id('DemoHelloWorld')
            .fontSize(28)
            .fontWeight(FontWeight.Bold)
            .alignRules({
              center: { anchor: '\_\_container\_\_', align: VerticalAlign.Bottom },
              middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
            });
    
          DemoItem({ layer: this.layer })
            .alignRules({
              center: { anchor: '\_\_container\_\_', align: VerticalAlign.Top },
              middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
            });
    
          Row({ space: 20 }) {
            Canvas(this.context)
              .width(200)
              .height(200)
              .borderWidth(2)
              .onClick(() => this.onButtonClick());
    
            Canvas(this.context2)
              .width(100)
              .height(100)
              .borderWidth(2);
          }
          .alignRules({
            center: { anchor: '\_\_container\_\_', align: VerticalAlign.Center },
            middle: { anchor: '\_\_container\_\_', align: HorizontalAlign.Center }
          });
    
        }
        .height('100%')
        .width('100%');
      }
    
      onButtonClick(): void {
        console.info('onButtonClick');
        this.layer.draw();
        this.layer.data = this.layer.context.getImageData(0, 0, WH, WH);
        this.context.putImageData(this.layer.data, 0, 0);
      _  // 此句用于证明layer中的绘画数据可以被多个context使用_
        this.context2.putImageData(this.layer.data, 0, 0);
      }
    }
    
    @Component
    struct DemoItem {
      private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(new RenderingContextSettings(false));
      @ObjectLink @Watch('change') layer: DemoLayer;
    
      change() {
        this.context.putImageData(this.layer.data, 0, 0);
      }
    
      build() {
        Stack() {
          Canvas(this.context)
            .width('100%')
            .height('100%');
        }
        .width(60)
        .height(60)
        .borderWidth(2);
      }
    }