DEV Community

HarmonyOS
HarmonyOS

Posted on

Allowing Canvas Drawing Beyond Parent Component Boundaries

Read the original article:Allowing Canvas Drawing Beyond Parent Component Boundaries

Context

In HarmonyOS development, developers may want to allow a Canvas component to render content beyond the bounds of its parent container. However, they often need to restrict the touch response area to remain within the parent’s limits. A common question arises: can the .clip property achieve this effect?

Description

  • The .clip property in HarmonyOS determines whether child components are clipped to the bounds of their parent. When set to true, any overflow content is visually and interactively clipped.
  • The .responseRegion property allows developers to define a custom touch response area for a component, which can be larger or smaller than its layout bounds.
  • The Canvas component provides a drawing surface, and CanvasRenderingContext2D is used to render shapes, text, and images on it.

Solution

The .clip property alone cannot achieve the desired behavior. When .clip is set to true, overflow content is not only visually clipped but also loses gesture responsiveness. Setting .clip to undefined disables clipping but does not control touch behavior.

To allow drawing beyond the parent component while restricting the touch-down response area, use the .responseRegion property. Here's how:

  • Wrap the Canvas in a parent Column with dimensions (150×150).
  • Set the Canvas size to (300×300) to allow drawing beyond the parent.
  • Define a .responseRegion of { x: 75, y: 0, width: 150, height: 150 } so that only the central area responds to touch-down events.
  • Drawing can begin in the blue (responsive) area and extend into the red (non-responsive) area, but cannot start in the red zone.

Sample Code:

import { hilog } from '@kit.PerformanceAnalysisKit'

@Entry
@Component
struct DrawBankCom {
  @State paintSize: number = 5
  @State paintColor: Color = Color.Black
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  tempPath: Path2D = new Path2D()
  @State @Watch('onChange') pathArray: Array<Path2D> = []
  @State isUpdate: boolean = true
  @State removeArray: Array<Path2D> = []
  @State text: string = ''
  @State eventType: string = ''
  @State arr: Array<number> = []

  build() {
    Column() {
      if (this.isUpdate) {
        Row() {
          Column() {
            Canvas(this.canvasContext)
              .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)
              .responseRegion({
                x: 75,
                y: 0,
                width: 150,
                height: 150
              })
          }
          .width(150)
          .height(150)
          .backgroundColor('#ffbdf3e9')
        }
      }

      Column() {
        Row() {
          Button('Undo').onClick(() => {
            this.pathArray.pop()
            hilog.info(0x0000, 'ccTest', this.pathArray.length + '')
          })
        }
        Text(this.text)
      }
    }
    .width(300)
    .height(300)
    .clip(false)
    .borderRadius(10)
    .backgroundColor('#ff0000')
  }

  onChange(changedPropertyName?: string) {
    this.canvasContext.reset()
    this.canvasContext.lineWidth = this.paintSize
    for (let index = 0; index < this.pathArray.length; index++) {
      this.canvasContext.stroke(this.pathArray[index])
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • The .clip property cannot selectively allow drawing overflow while restricting gesture response.
  • Use .responseRegion to define a custom touch-sensitive area for the Canvas.
  • Drawing can extend beyond the parent component, but touch-down events will only register within the defined region.
  • This approach enables flexible drawing behavior while maintaining controlled interaction zones.

Written by Emincan Ozcan

Top comments (0)