如何让Canvas绘制内容能超出父组件的限制
======================

#### 问题现象

如何让Canvas绘制内容能超出父组件的限制，“按下”的响应区域不超出父组件范围？组件中.clip属性能实现此效果吗？

#### 背景知识

*   HarmonyOS组件中.clip属性：是否对子组件超出当前组件范围外的区域进行裁剪。参考链接：[clip](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-universal-attributes-sharp-clipping#clip12)。
*   HarmonyOS组件中.responseRegion属性可以实现组件的响应区域范围的变化，响应区域范围可以超出或者小于组件的布局范围。参考链接：[自定义控制的多层级手势事件](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-gesture-events-multi-level-gesture#自定义控制的多层级手势事件)。
*   [Canvas](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ui-js-components-canvas)：Canvas组件提供画布，用于自定义绘制图形。
*   [CanvasRenderingContext2D](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-components-canvas-canvasrenderingcontext2d)：使用CanvasRenderingContext2D在Canvas画布组件上进行绘制，绘制对象可以是矩形、文本、图片等。

#### 解决方案

在HarmonyOS中，无法通过.clip属性实现所需效果。当.clip属性设置为true时，子组件超出当前组件范围的区域将不会响应绑定的手势事件；当设置为undefined时，系统将不再对超出部分进行裁剪，但这并不意味着可以控制内容超出组件本身的绘制范围。可以通过以下方案实现让Canvas绘制内容能超出父组件限制的功能，步骤如下：

1.  使用Column父组件包含子组件Canvas，Column父组件宽高为(150,150)区域，子组件Canvas宽高为(300,300)。
2.  对Canvas组件设置.responseRegion属性触摸热区为.responseRegion({ x: 75, y: 0, width: 150, height: 150 })。实现功能：蓝色区域（150\*150）可响应Canvas绘制内容时“按下（down）”的触摸操作，红色区域（300\*300去除蓝色区域的范围）不可响应“按下（down）”的触摸操作，红色区域只可响应从蓝色区域“按下（down）”后的“移动（move）”触摸操作。

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/cb/v3/dzeu22LtSPisEwhnlzTNaw/note_3.0-zh-cn.png?HW-CC-KV=V1&HW-CC-Date=20260709T134056Z&HW-CC-Expire=86400&HW-CC-Sign=6D7AC16FD336215ED719C4CAC0C976890AD85073B472EA3CB904C6C5D2A44D48)

当组件Canvas绑定了.responseRegion(Rect)，所有落在Rect区域范围的触摸事件和手势可被组件Canvas对应的回调响应。

@Entry
@Component
struct DrawBankCom {
  paintSize: number \= 5; _//_ _当前画笔大小_
  private settings: RenderingContextSettings \= new RenderingContextSettings(true);
  canvasContext: CanvasRenderingContext2D \= new CanvasRenderingContext2D(this.settings);
  tempPath: Path2D \= new Path2D();
  @State @Watch('onChange') pathArray: Array<Path2D\> = \[\]; _// 所有画图路径信息_
  isUpdate: boolean \= true;
  removeArray: Array<Path2D\> = \[\]; _//_ _回退的路径集合_
  @State text: string \= '';
  @State eventType: string \= '';

  build() {
    Column() {
      if (this.isUpdate) {
        Row() {
          Column() {
            Canvas(this.canvasContext)
            _  // Canvas(子组件)宽高设置为300\*300_
              .width(300)
              .height(300)
              .onReady(() \=\> {
                this.canvasContext.lineWidth \= this.paintSize;
                this.canvasContext.stroke(this.tempPath);
                for (let index \= 0; index < this.pathArray.length; index++) {
                  this.canvasContext.stroke(this.pathArray\[index\]);
                }
              })
              .onTouch((event?: TouchEvent) \=\> {
                if (event) {
                  if (event.type \=== TouchType.Down) {
                    this.eventType \= 'Down';
                    this.canvasContext.lineWidth \= this.paintSize;
                    this.canvasContext.beginPath();
                    this.tempPath \= new Path2D();
                    this.pathArray.push(this.tempPath);
                    this.tempPath.moveTo(event.touches\[0\].x, event.touches\[0\].y);
                    this.canvasContext.moveTo(event.touches\[0\].x, event.touches\[0\].y);
                  }
                  if (event.type \=== TouchType.Up) {
                    this.eventType \= 'Up';
                  }
                  if (event.type \=== TouchType.Move) {
                    this.eventType \= 'Move';
                    this.tempPath.lineTo(event.touches\[0\].x, event.touches\[0\].y);
                    this.canvasContext.stroke(this.tempPath);
                    this.canvasContext.lineTo(event.touches\[0\].x, event.touches\[0\].y);
                    this.canvasContext.stroke();
                  }
                  this.text \= 'TouchType:' \+ this.eventType \+ '\\n touch point and touch element:\\nx: ' +
                  event.touches\[0\].x \+ '\\n' \+ 'y: ' \+ event.touches\[0\].y \+ '\\nwidth:' +
                  event.target.area.width \+ '\\nheight:' \+ event.target.area.height \+ '\\pathArray size:' +
                  this.pathArray.length;
                }
              })
              .renderFit(RenderFit.TOP\_RIGHT)
               _ // 设置触摸热区，蓝色区域可响应“down”操作，红色不可响应“down”操作，只能响应“move”操作_
              .responseRegion({
                x: 75,
                y: 0,
                width: 150,
                height: 150
              });
          }
        _  // Column(父组件)宽高设置为150\*150_
          .width(150)
          .height(150)
          .backgroundColor('#ffbdf3e9');
        };
      }

      Column() {
        Row() {
          Button('回退').onClick(() \=\> {
            this.pathArray.pop();
          });
        };

        Text(this.text);
      };
    }
    .width(300)
    .height(300)
    .clip(false)
    .borderRadius(10)
    .backgroundColor('#ff0000');
  }

  onChange() {
    this.canvasContext.reset();
    this.canvasContext.lineWidth \= this.paintSize;
    for (let index \= 0; index < this.pathArray.length; index++) {
      this.canvasContext.stroke(this.pathArray\[index\]);
    }
  }
}

具体表现为从蓝色区域开始画线，可以延伸到红色区域，但是不可从红色区域开始画线。

#### 总结

使用.responseRegion属性可以实现组件的响应区域范围的变化，能实现让Canvas绘制内容能超出父组件的限制。