• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 同层渲染
2<!--Kit: ArkWeb-->
3<!--Subsystem: Web-->
4<!--Owner: @ding-xin88-->
5<!--Designer: @LongLie-->
6<!--Tester: @ghiker-->
7<!--Adviser: @HelloCrease-->
8
9在系统中,应用可以使用Web组件加载Web网页。当非系统框架的UI组件功能或性能不如系统组件时,可使用同层渲染技术,通过ArkUI组件渲染这些组件(简称为同层组件)。
10
11## 使用场景
12### Web网页
13小程序的地图组件,可以使用ArkUI的XComponent组件渲染来提升性能。小程序的输入框组件,可以使用ArkUI的TextInput组件渲染,达到与系统应用一致的输入体验。
14- 在网页侧,应用开发者可将&lt;embed&gt;、&lt;object&gt;的网页UI组件(简称为同层标签),按一定规则进行同层渲染,详细规格见同层渲染规格小节。
15
16- 在应用侧,应用开发者可以通过Web组件的同层渲染事件上报接口,感知到H5同层标签的生命周期以及输入事件,进行同层渲染组件的相应业务逻辑处理。
17
18- 在应用侧,应用开发者可以使用ArkUI的NodeContainer等接口,构建H5同层标签对应的同层渲染组件。可支持同层渲染的ArkUI常用组件包括:[TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md), [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), [Canvas](../reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md), [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [Web](../reference/apis-arkweb/arkts-basic-components-web.md)。具体规格可参见[同层渲染规格小节](#规格约束)。
19
20### 三方UI框架
21Flutter提供了PlatformView与Texture抽象组件,这些组件可使用系统组件渲染,用来支持Flutter组件功能不足的部分。Weex2.0框架的Camera、Video和Canvas组件可以使用系统组件渲染,以增强功能和性能。
22
23- 在三方框架页面侧,由于Flutter、Weex等三方框架不在操作系统范围内,本文不列举可被同层渲染的三方框架UI组件的范围与使用方式。
24
25- 在应用侧,应用开发者可以使用ArkUI的NodeContainer等接口,构建三方框架同层标签对应的同层渲染组件。可支持同层渲染的ArkUI常用组件包括:[TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md), [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), [Canvas](../reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md), [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [Web](../reference/apis-arkweb/arkts-basic-components-web.md)。具体规格可参见[同层渲染规格](#规格约束)。
26
27## 整体架构
28ArkWeb同层渲染特性主要提供两种能力:同层标签生命周期和事件命中转发处理。
29
30同层标签生命周期主要关联前端标签(&lt;embed&gt;/&lt;object&gt;),同时命中到同层标签的事件会被上报到开发者侧,由开发者分发到对应组件树。整体框架如下图所示:
31
32**图1** 同层渲染整体架构
33
34![web-same-layer](figures/web-same-layer-develop-architecture.png)
35
36## 规格约束
37### 可被同层渲染的ArkUI组件
38
39以下规格对Web网页和三方框架场景均生效。
40
41**支持的组件范围:**
42
43- 基础组件:[AlphabetIndexer](../reference/apis-arkui/arkui-ts/ts-container-alphabet-indexer.md), [Blank](../reference/apis-arkui/arkui-ts/ts-basic-components-blank.md), [Button](../reference/apis-arkui/arkui-ts/ts-basic-components-button.md), [CalendarPicker](../reference/apis-arkui/arkui-ts/ts-basic-components-calendarpicker.md), [Checkbox](../reference/apis-arkui/arkui-ts/ts-basic-components-checkbox.md), [CheckboxGroup](../reference/apis-arkui/arkui-ts/ts-basic-components-checkboxgroup.md), [ContainerSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-containerspan.md), [DataPanel](../reference/apis-arkui/arkui-ts/ts-basic-components-datapanel.md), [DatePicker](../reference/apis-arkui/arkui-ts/ts-basic-components-datepicker.md), [Divider](../reference/apis-arkui/arkui-ts/ts-basic-components-divider.md), [Gauge](../reference/apis-arkui/arkui-ts/ts-basic-components-gauge.md), [Hyperlink](../reference/apis-arkui/arkui-ts/ts-container-hyperlink.md), [Image](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md), [ImageAnimator](../reference/apis-arkui/arkui-ts/ts-basic-components-imageanimator.md), [ImageSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-imagespan.md), [LoadingProgress](../reference/apis-arkui/arkui-ts/ts-basic-components-loadingprogress.md), [Marquee](../reference/apis-arkui/arkui-ts/ts-basic-components-marquee.md), [PatternLock](../reference/apis-arkui/arkui-ts/ts-basic-components-patternlock.md), [Progress](../reference/apis-arkui/arkui-ts/ts-basic-components-progress.md), [QRCode](../reference/apis-arkui/arkui-ts/ts-basic-components-qrcode.md), [Radio](../reference/apis-arkui/arkui-ts/ts-basic-components-radio.md), [Rating](../reference/apis-arkui/arkui-ts/ts-basic-components-rating.md), [Refresh](../reference/apis-arkui/arkui-ts/ts-container-refresh.md), [ScrollBar](../reference/apis-arkui/arkui-ts/ts-container-scroll.md), [Search](../reference/apis-arkui/arkui-ts/ts-basic-components-search.md), [Span](../reference/apis-arkui/arkui-ts/ts-basic-components-span.md), [Select](../reference/apis-arkui/arkui-ts/ts-basic-components-select.md), [Slider](../reference/apis-arkui/arkui-ts/ts-basic-components-slider.md), [Text](../reference/apis-arkui/arkui-ts/ts-basic-components-text.md), [TextArea](../reference/apis-arkui/arkui-ts/ts-basic-components-textarea.md), [TextClock](../reference/apis-arkui/arkui-ts/ts-basic-components-textclock.md), [TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md), [TextPicker](../reference/apis-arkui/arkui-ts/ts-basic-components-textpicker.md), [TextTimer](../reference/apis-arkui/arkui-ts/ts-basic-components-texttimer.md), [TimePicker](../reference/apis-arkui/arkui-ts/ts-basic-components-timepicker.md), [Toggle](../reference/apis-arkui/arkui-ts/ts-basic-components-toggle.md)
44
45- 容器类组件:[Badge](../reference/apis-arkui/arkui-ts/ts-container-badge.md), [Column](../reference/apis-arkui/arkui-ts/ts-container-column.md), [ColumnSplit](../reference/apis-arkui/arkui-ts/ts-container-columnsplit.md), [Counter](../reference/apis-arkui/arkui-ts/ts-container-counter.md), [Flex](../reference/apis-arkui/arkui-ts/ts-container-flex.md), [GridCol](../reference/apis-arkui/arkui-ts/ts-container-gridcol.md), [GridRow](../reference/apis-arkui/arkui-ts/ts-container-gridrow.md), [Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md), [GridItem](../reference/apis-arkui/arkui-ts/ts-container-griditem.md),[List](../reference/apis-arkui/arkui-ts/ts-container-list.md), [ListItem](../reference/apis-arkui/arkui-ts/ts-container-listitem.md), [ListItemGroup](../reference/apis-arkui/arkui-ts/ts-container-listitemgroup.md), [RelativeContainer](../reference/apis-arkui/arkui-ts/ts-container-relativecontainer.md), [Row](../reference/apis-arkui/arkui-ts/ts-container-row.md), [RowSplit](../reference/apis-arkui/arkui-ts/ts-container-rowsplit.md), [Scroll](../reference/apis-arkui/arkui-ts/ts-container-scroll.md), [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md), [Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md), [Tabs](../reference/apis-arkui/arkui-ts/ts-container-tabs.md), [TabContent](../reference/apis-arkui/arkui-ts/ts-container-tabcontent.md), [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), [SideBarContainer](../reference/apis-arkui/arkui-ts/ts-container-sidebarcontainer.md), [Stepper](../reference/apis-arkui/arkui-ts/ts-basic-components-stepper.md), [StepperItem](../reference/apis-arkui/arkui-ts/ts-basic-components-stepperitem.md), [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md), [FlowItem](../reference/apis-arkui/arkui-ts/ts-container-flowitem.md)
46
47- 自绘制类组件:[XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), [Canvas](../reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md), [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [Web](../reference/apis-arkweb/arkts-basic-components-web.md)
48
49- 命令式自定义绘制节点:[BuilderNode](../reference/apis-arkui/js-apis-arkui-builderNode.md), [ComponentContent](../reference/apis-arkui/js-apis-arkui-ComponentContent.md), [ContentSlot](../reference/apis-arkui/arkui-ts/ts-components-contentSlot.md), [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md), [Graphics](../reference/apis-arkui/js-apis-arkui-graphics.md), [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md), [RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md), [XComponentNode](../reference/apis-arkui/js-apis-arkui-xcomponentNode.md), [AttributeUpdater](../reference/apis-arkui/js-apis-arkui-AttributeUpdater.md), [CAPI](../reference/apis-arkui/capi-arkui-nativemodule.md)(支持同层渲染的组件范围同ArkTS)
50
51**支持的组件通用属性与事件:**
52
53- 不支持的通用属性:[分布式迁移标识](../reference/apis-arkui/arkui-ts/ts-universal-attributes-restoreId.md),[特效绘制合并](../reference/apis-arkui/arkui-ts/ts-universal-attributes-use-effect.md)。
54
55- 其他未明确标注不支持的属性与事件及组件能力,均默认支持。
56
57### Web网页的同层渲染标签
58此规格仅针对Web网页,不适用于三方框架场景。
59
60如果应用需要在Web组件加载的网页中使用同层渲染,需要按照以下规格将网页中的&lt;embed&gt;、&lt;object&gt;标签指定为同层渲染组件。
61
62**支持的H5标签:**
63- 支持&lt;embed&gt;标签:在开启同层渲染后,仅支持type类型为native前缀的标签识别为同层组件,不支持自定义属性。
64
65- 支持&lt;object&gt;标签:在开启同层渲染后,支持将非标准MIME type的object标签识别为同层组件,支持通过param/value的自定义属性解析。
66
67- 不支持W3C规范标准标签(如&lt;input&gt;、&lt;video&gt;)定义为同层标签。
68
69- 不支持同时配置&lt;object&gt;标签和&lt;embed&gt;标签作为同层标签。
70
71- 标签类型只支持英文字符,不区分大小写。
72
73**同层标签支持的css属性:**
74
75display,position,z-index,visibility,opacity, background-color,background-image,width,height,padding,padding-left,padding-top,padding-right,padding-bottom,margin,margin-left,margin-top,margin-right,margin-bottom,border-width,border-style,border-color,border-left-width,border-left-style,border-left-color,border-top-width,border-top-style,border-top-color,border-right-width,border-right-style,border-right-color,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-right,border-top,border-bottom,border,border-top-left-radius,border-top-right-radius,border-bottom-left-radius,border-bottom-right-radius,border-radius,transition,transform(仅支持translate/scale,scale对应参数只支持大于等于0的值)
76
77 除上面支持的css属性范围,其他的css属性均不保证符合预期,比如transform属性中的rotate,skew等。
78
79**同层标签的生命周期管理:**
80当同层标签生命周期变化时触发[onNativeEmbedLifecycleChange()](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedlifecyclechange11)回调。
81
82- 支持创建、销毁、位置宽高变化。
83
84- 支持同层组件所在Web页面进入前进后退缓存。
85
86**同层标签的输入事件分发处理:**
87- 支持触摸事件TouchEvent的DOWN/UP/MOVE/CANCEL。支持[配置触摸事件消费结果](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedgestureevent11),默认为应用侧消费。
88
89- 不支持同层标签所在的应用页面缩放和[initialScale](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#initialscale9)、[zoom](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#zoom)、[zoomIn](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#zoomin)、[zoomOut](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#zoomout)等缩放接口。
90
91- 暂不支持鼠标、键盘、触摸板事件上报。
92
93- 支持默认将鼠标和触摸板左键事件(MousePress/MouseRelease/MouseMOVE)转换为触摸事件(TouchDOWN/TouchUP/TouchMOVE)上报。
94
95**同层标签的可见状态变化:**
96当同层标签可见状态变化时触发[onNativeEmbedVisibilityChange](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedvisibilitychange12)回调。
97
98- 支持同层标签相对于视口的可见状态上报。
99
100- 默认不支持由于同层标签CSS样式或尺寸变化导致的可见状态变化上报,具体规格参考[onNativeEmbedVisibilityChange](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedvisibilitychange12)。
101
102**约束限制:**
103
104- Web页面内同层标签数量应控制在5个以内。超过5个,渲染性能将会下降。
105
106- 受GPU限制,同层标签最大高度不超过8000px,最大纹理大小为8000px。
107
108- 开启同层渲染后,Web组件打开的所有Web页面将不支持同步渲染模式[RenderMode](../reference/apis-arkweb/arkts-basic-components-web-e.md#rendermode12)。
109
110- Video组件:在非全屏Video变为全屏时,Video组件变为非纹理导出模式,视频播放状态保持延续;恢复为非全屏时,变为纹理导出模式,视频播放状态保持延续。
111
112- Web组件:仅支持一层同层渲染嵌套,不支持多层同层渲染嵌套。输入事件只支持滑动、点击、长按,不支持拖拽、旋转、缩放。
113
114- 涉及界面交互的ArkUI组件(如TextInput等):建议在页面布局中使用Stack包裹同层组件容器与BuilderNode,并使两者位置一致,NodeContainer要与&lt;embed&gt;/&lt;object&gt;标签对齐,以保障组件正常交互。如两者位置不一致,可能出现的问题有:TextInput/TextArea等附属的文本选择框位置错位(如下图)、LoadingProgress/Marquee等组件的动画启停与组件可见状态不匹配。
115
116  **图2** 未使用Stack包裹,TextInput的位置错位
117
118  ![web-same-layer-develop](figures/web-same-layer-develop-textinput1.png)
119
120  **图3** 使用Stack包裹,TextInput的位置正常
121
122  ![web-same-layer-develop](figures/web-same-layer-develop-textinput2.png)
123
124## Web页面中同层渲染输入框
125在Web页面中,可以使用ArkUI系统的TextInput组件进行同层渲染。此处利用同层渲染展示三个输入框,渲染效果图如下:
126
127**图4** 同层渲染输入框
128
129  ![web-same-layer-develop](figures/web-same-layer-develop-input.png)
130
1311. 在Web页面中标记需要同层渲染的HTML标签。
132
133   同层渲染支持&lt;embed&gt;/&lt;object&gt;两种标签。type类型可任意指定,两个字符串参数均不区分大小写,ArkWeb内核将会统一转换为小写。其中,tag字符串使用全字符串匹配,type使用字符串前缀匹配。
134
135   若开发者不使用该接口或该接口接收的为非法字符串(空字符串)时,ArkWeb内核将使用默认设置,即"embed" + "native/"前缀模式。若指定类型与w3c定义的&lt;embed&gt;或&lt;object&gt;标准类型重合,如registerNativeEmbedRule("object", "application/pdf"),ArkWeb将遵循w3c标准行为,不会将其识别为同层标签。
136
137   - 采用&lt;embed&gt;标签。
138
139     ```html
140     <!--HAP's src/main/resources/rawfile/text.html-->
141     <!DOCTYPE html>
142     <html>
143     <head>
144         <title>同层渲染html</title>
145         <meta name="viewport">
146     </head>
147
148     <body style="background:white">
149
150     <embed id = "input1" type="native/view" style="width: 100%; height: 100px; margin: 30px; margin-top: 600px"/>
151
152     <embed id = "input2" type="native/view2" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"/>
153
154     <embed id = "input3" type="native/view3" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"/>
155
156     </body>
157     </html>
158     ```
159
160   - 采用&lt;object&gt;标签。
161
162     需要使用registerNativeEmbedRule注册object标签。
163     ```ts
164     // ...
165     Web({src: $rawfile("text.html"), controller: this.browserTabController})
166       // 注册同层标签为"object",类型为"test"前缀。
167       .registerNativeEmbedRule("object", "test")
168       // ...
169     ```
170
171     与registerNativeEmbedRule相对应的前端页面代码,类型可使用"test"及以"test"为前缀的字串。
172
173      ```html
174      <!--HAP's src/main/resources/rawfile/text.html-->
175      <!DOCTYPE html>
176      <html>
177      <head>
178          <title>同层渲染html</title>
179          <meta name="viewport">
180      </head>
181
182      <body style="background:white">
183
184      <object id = "input1" type="test/input" style="width: 100%; height: 100px; margin: 30px; margin-top: 600px"></object>
185
186      <object id = "input2" type="test/input" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"></object>
187
188      <object id = "input3" type="test/input" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"></object>
189
190      </body>
191      </html>
192      ```
193
1942. 在应用侧开启同层渲染功能。
195
196   同层渲染功能默认不开启,如果要使用同层渲染的功能,可通过[enableNativeEmbedMode](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#enablenativeembedmode11)来开启。
197
198   ```ts
199   // xxx.ets
200   import { webview } from '@kit.ArkWeb';
201   @Entry
202   @Component
203   struct WebComponent {
204     controller: webview.WebviewController = new webview.WebviewController();
205
206     build() {
207       Column() {
208         Web({ src: 'www.example.com', controller: this.controller })
209           // 配置同层渲染开关开启。
210           .enableNativeEmbedMode(true)
211       }
212     }
213   }
214   ```
215
2163. 创建自定义组件。
217
218   同层渲染功能开启后,展示在对应区域的系统组件。
219
220   ```ts
221   @Component
222   struct TextInputComponent {
223     @Prop params: Params
224     @State bkColor: Color = Color.White
225
226     build() {
227       Column() {
228         TextInput({text: '', placeholder: 'please input your word...'})
229           .placeholderColor(Color.Gray)
230           .id(this.params?.elementId)
231           .placeholderFont({size: 13, weight: 400})
232           .caretColor(Color.Gray)
233           .width(this.params?.width)
234           .height(this.params?.height)
235           .fontSize(14)
236           .fontColor(Color.Black)
237       }
238       //自定义组件中的最外层容器组件宽高应该为同层标签的宽高。
239       .width(this.params.width)
240       .height(this.params.height)
241     }
242   }
243
244   @Builder
245   function TextInputBuilder(params:Params) {
246     TextInputComponent({params: params})
247       .width(params.width)
248       .height(params.height)
249       .backgroundColor(Color.White)
250   }
251   ```
252
2534. 创建节点控制器。
254
255   用于控制和反馈对应NodeContainer上的节点行为。
256
257   ```ts
258   class MyNodeController extends NodeController {
259     private rootNode: BuilderNode<[Params]> | undefined | null;
260     private embedId_: string = "";
261     private surfaceId_: string = "";
262     private renderType_: NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY;
263     private width_: number = 0;
264     private height_: number = 0;
265     private type_: string = "";
266     private isDestroy_: boolean = false;
267
268     setRenderOption(params: NodeControllerParams) {
269       this.surfaceId_ = params.surfaceId;
270       this.renderType_ = params.renderType;
271       this.embedId_ = params.embedId;
272       this.width_ = params.width;
273       this.height_ = params.height;
274       this.type_ = params.type;
275     }
276
277     // 必须要重写的方法,用于构建节点数、返回节点数挂载在对应NodeContainer中。
278     // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新。
279     makeNode(uiContext: UIContext): FrameNode | null {
280       if (this.isDestroy_) { // rootNode为null。
281         return null;
282       }
283       if (!this.rootNode) {// rootNode 为undefined时。
284         this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_ });
285         if(this.rootNode) {
286           this.rootNode.build(wrapBuilder(TextInputBuilder), {  textOne: "myTextInput", width: this.width_, height: this.height_  })
287           return this.rootNode.getFrameNode();
288         }else{
289           return null;
290         }
291       }
292       // 返回FrameNode节点。
293       return this.rootNode.getFrameNode();
294     }
295
296     updateNode(arg: Object): void {
297       this.rootNode?.update(arg);
298     }
299
300     getEmbedId(): string {
301       return this.embedId_;
302     }
303
304     setDestroy(isDestroy: boolean): void {
305       this.isDestroy_ = isDestroy;
306       if (this.isDestroy_) {
307         this.rootNode = null;
308       }
309     }
310
311     postEvent(event: TouchEvent | undefined): boolean {
312       return this.rootNode?.postTouchEvent(event) as boolean
313     }
314   }
315   ```
316
3175. 监听同层渲染的生命周期变化。
318
319   开启该功能后,当网页中存在同层渲染支持的标签时,ArkWeb内核会触发由[onNativeEmbedLifecycleChange](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedlifecyclechange11)注册的回调函数。
320
321   开发者则需要调用[onNativeEmbedLifecycleChange](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedlifecyclechange11)来监听同层渲染标签的生命周期变化。
322
323    ```ts
324    build() {
325      Row() {
326        Column() {
327          Stack() {
328            ForEach(this.componentIdArr, (componentId: string) => {
329              NodeContainer(this.nodeControllerMap.get(componentId))
330                .position(this.positionMap.get(componentId))
331                .width(this.widthMap.get(componentId))
332                .height(this.heightMap.get(componentId))
333            }, (embedId: string) => embedId)
334            // Web组件加载本地text.html页面。
335            Web({src: $rawfile("text.html"), controller: this.browserTabController})
336              // 配置同层渲染开关开启。
337              .enableNativeEmbedMode(true)
338                // 注册同层标签为<object>,类型为"test"前缀。
339              .registerNativeEmbedRule("object", "test")
340                // 获取<embed>标签的生命周期变化数据。
341              .onNativeEmbedLifecycleChange((embed) => {
342                console.info("NativeEmbed surfaceId" + embed.surfaceId);
343                // 如果使用embed.info.id作为映射nodeController的key,请在h5页面显式指定id。
344                const componentId = embed.info?.id?.toString() as string
345                if (embed.status == NativeEmbedStatus.CREATE) {
346                  console.info("NativeEmbed create" + JSON.stringify(embed.info));
347                  // 创建节点控制器、设置参数并rebuild。
348                  let nodeController = new MyNodeController()
349                  // embed.info.widthembed.info.height单位是px格式,需要转换成ets侧的默认单位vp。
350                  nodeController.setRenderOption({surfaceId : embed.surfaceId as string,
351                    type : embed.info?.type as string,
352                    renderType : NodeRenderType.RENDER_TYPE_TEXTURE,
353                    embedId : embed.embedId as string,
354                    width : this.uiContext.px2vp(embed.info?.width),
355                    height : this.uiContext.px2vp(embed.info?.height)})
356                  this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`}
357                  nodeController.setDestroy(false);
358                  //根据web传入的embed的id属性作为key,将nodeController存入Map。
359                  this.nodeControllerMap.set(componentId, nodeController);
360                  this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width));
361                  this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height));
362                  this.positionMap.set(componentId, this.edges);
363                  // 将web传入的embed的id属性存入@State状态数组变量中,用于动态创建nodeContainer节点容器,需要将push动作放在set之后。
364                  this.componentIdArr.push(componentId)
365                } else if (embed.status == NativeEmbedStatus.UPDATE) {
366                  let nodeController = this.nodeControllerMap.get(componentId);
367                  console.info("NativeEmbed update" + JSON.stringify(embed));
368                  this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`}
369                  this.positionMap.set(componentId, this.edges);
370                  this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width));
371                  this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height));
372                  nodeController?.updateNode({textOne: 'update', width: this.uiContext.px2vp(embed.info?.width), height: this.uiContext.px2vp(embed.info?.height)} as ESObject)
373                } else if (embed.status == NativeEmbedStatus.DESTROY) {
374                  console.info("NativeEmbed destroy" + JSON.stringify(embed));
375                  let nodeController = this.nodeControllerMap.get(componentId);
376                  nodeController?.setDestroy(true);
377                  this.nodeControllerMap.delete(componentId);
378                  this.positionMap.delete(componentId);
379                  this.widthMap.delete(componentId);
380                  this.heightMap.delete(componentId);
381                  this.componentIdArr = this.componentIdArr.filter((value: string) => value !== componentId);
382                } else {
383                  console.info("NativeEmbed status" + embed.status);
384                }
385              })
386          }.height("80%")
387        }
388      }
389    }
390    ```
391
3926. 同层渲染手势事件。
393
394   开启该功能后,每当在同层渲染的区域进行触摸操作时,ArkWeb内核会触发[onNativeEmbedGestureEvent](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedgestureevent11)注册的回调函数。
395
396   开发者则需要调用[onNativeEmbedGestureEvent](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedgestureevent11)来监听同层渲染区域的手势事件。
397
398    ```ts
399    build() {
400      Row() {
401        Column() {
402          Stack() {
403            ForEach(this.componentIdArr, (componentId: string) => {
404              NodeContainer(this.nodeControllerMap.get(componentId))
405                .position(this.positionMap.get(componentId))
406                .width(this.widthMap.get(componentId))
407                .height(this.heightMap.get(componentId))
408            }, (embedId: string) => embedId)
409            // Web组件加载本地text.html页面。
410            Web({src: $rawfile("text.html"), controller: this.browserTabController})
411              // 配置同层渲染开关开启。
412              .enableNativeEmbedMode(true)
413                // 获取<embed>标签的生命周期变化数据。
414              .onNativeEmbedLifecycleChange((embed) => {
415                // 生命周期变化实现。
416              })
417              .onNativeEmbedGestureEvent((touch) => {
418                console.info("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent));
419                this.componentIdArr.forEach((componentId: string) => {
420                  let nodeController = this.nodeControllerMap.get(componentId);
421                  // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。
422                  if(nodeController?.getEmbedId() == touch.embedId) {
423                    let ret = nodeController?.postEvent(touch.touchEvent)
424                    if(ret) {
425                      console.info("onNativeEmbedGestureEvent success " + componentId);
426                    } else {
427                      console.info("onNativeEmbedGestureEvent fail " + componentId);
428                    }
429                    if(touch.result) {
430                      // 通知Web组件手势事件消费结果。
431                      touch.result.setGestureEventResult(ret);
432                    }
433                  }
434                })
435              })
436          }
437        }
438      }
439    }
440    ```
441
4427. 同层渲染鼠标事件
443
444   开启该功能后,在同层渲染的区域进行下述动作时,ArkWeb内核会触发[onNativeEmbedMouseEvent](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedmouseevent20)注册的回调函数:
445
446   - 使用鼠标左键、中键、右键进行点击或长按。
447   - 使用触摸板进行对应鼠标左键、中键、右键点击长按的操作。
448
449   开发者则需要调用[onNativeEmbedMouseEvent](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedmouseevent20)来监听同层渲染同层渲染区域的鼠标事件。
450
451    ```ts
452    build() {
453      Row() {
454        Column() {
455          Stack() {
456            ForEach(this.componentIdArr, (componentId: string) => {
457              NodeContainer(this.nodeControllerMap.get(componentId))
458                .position(this.positionMap.get(componentId))
459                .width(this.widthMap.get(componentId))
460                .height(this.heightMap.get(componentId))
461            }, (embedId: string) => embedId)
462            // Web组件加载本地text.html页面。
463            Web({src: $rawfile("text.html"), controller: this.browserTabController})
464              // 配置同层渲染开关开启。
465              .enableNativeEmbedMode(true)
466                // 获取<embed>标签的生命周期变化数据。
467              .onNativeEmbedLifecycleChange((embed) => {
468                // 生命周期变化实现。
469              })
470              .onNativeEmbedGestureEvent((touch) => {
471                // 处理同层渲染手势事件。
472              })
473              .onNativeEmbedMouseEvent((mouse) => {
474                console.info("NativeEmbed onNativeEmbedMouseEvent" + JSON.stringify(mouse.mouseEvent));
475                this.componentIdArr.forEach((componentId: string) => {
476                  let nodeController = this.nodeControllerMap.get(componentId);
477                  // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。
478                  if(nodeController?.getEmbedId() == mouse.embedId) {
479                    let ret = nodeController?.postInputEvent(mouse.mouseEvent)
480                    if(ret) {
481                      console.info("onNativeEmbedMouseEvent success " + componentId);
482                    } else {
483                      console.info("onNativeEmbedMouseEvent fail " + componentId);
484                    }
485                    if(mouse.result) {
486                      // 通知Web组件鼠标事件消费结果。
487                      mouse.result.setMouseEventResult(ret);
488                    }
489                  }
490                })
491              })
492          }
493        }
494      }
495    }
496    ```
497**完整示例:**
498
499使用前请在module.json5中添加网络权限,添加方法请参考[在配置文件中声明权限](../security/AccessToken/declare-permissions.md#在配置文件中声明权限)。
500
501  ```
502  "requestPermissions":[
503      {
504        "name" : "ohos.permission.INTERNET"
505      }
506    ]
507  ```
508
509应用侧代码。
510
511  ```ts
512  // 创建NodeController
513  import { webview } from '@kit.ArkWeb';
514  import { UIContext } from '@kit.ArkUI';
515  import { NodeController, BuilderNode, NodeRenderType, FrameNode } from '@kit.ArkUI';
516
517  @Observed
518  declare class Params{
519    elementId: string
520    textOne: string
521    textTwo: string
522    width: number
523    height: number
524  }
525
526  declare class NodeControllerParams {
527    surfaceId: string
528    type: string
529    renderType: NodeRenderType
530    embedId: string
531    width: number
532    height: number
533  }
534
535  // 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用。
536  class MyNodeController extends NodeController {
537    private rootNode: BuilderNode<[Params]> | undefined | null;
538    private embedId_: string = "";
539    private surfaceId_: string = "";
540    private renderType_: NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY;
541    private width_: number = 0;
542    private height_: number = 0;
543    private type_: string = "";
544    private isDestroy_: boolean = false;
545
546    setRenderOption(params: NodeControllerParams) {
547      this.surfaceId_ = params.surfaceId;
548      this.renderType_ = params.renderType;
549      this.embedId_ = params.embedId;
550      this.width_ = params.width;
551      this.height_ = params.height;
552      this.type_ = params.type;
553    }
554
555    // 必须要重写的方法,用于构建节点数、返回节点数挂载在对应NodeContainer中。
556    // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新。
557    makeNode(uiContext: UIContext): FrameNode | null {
558      if (this.isDestroy_) { // rootNode为null。
559        return null;
560      }
561      if (!this.rootNode) {// rootNode 为undefined时。
562        this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_ });
563        if(this.rootNode) {
564          this.rootNode.build(wrapBuilder(TextInputBuilder), {  textOne: "myTextInput", width: this.width_, height: this.height_  })
565          return this.rootNode.getFrameNode();
566        }else{
567          return null;
568        }
569      }
570      // 返回FrameNode节点。
571      return this.rootNode.getFrameNode();
572    }
573
574    updateNode(arg: Object): void {
575      this.rootNode?.update(arg);
576    }
577
578    getEmbedId(): string {
579      return this.embedId_;
580    }
581
582    setDestroy(isDestroy: boolean): void {
583      this.isDestroy_ = isDestroy;
584      if (this.isDestroy_) {
585        this.rootNode = null;
586      }
587    }
588
589    postEvent(event: TouchEvent | undefined): boolean {
590      return this.rootNode?.postTouchEvent(event) as boolean
591    }
592
593    postInputEvent(event: MouseEvent | undefined): boolean {
594      return this.rootNode?.postInputEvent(event) as boolean
595    }
596  }
597
598  @Component
599  struct TextInputComponent {
600    @Prop params: Params
601    @State bkColor: Color = Color.White
602
603    build() {
604      Column() {
605        TextInput({text: '', placeholder: 'please input your word...'})
606          .placeholderColor(Color.Gray)
607          .id(this.params?.elementId)
608          .placeholderFont({size: 13, weight: 400})
609          .caretColor(Color.Gray)
610          .fontSize(14)
611          .fontColor(Color.Black)
612      }
613      //自定义组件中的最外层容器组件宽高应该为同层标签的宽高。
614      .width(this.params.width)
615      .height(this.params.height)
616    }
617  }
618
619  // @Builder中为动态组件的具体组件内容。
620  @Builder
621  function TextInputBuilder(params:Params) {
622    TextInputComponent({params: params})
623      .width(params.width)
624      .height(params.height)
625      .backgroundColor(Color.White)
626  }
627
628  @Entry
629  @Component
630  struct Page{
631    browserTabController: WebviewController = new webview.WebviewController()
632    private nodeControllerMap: Map<string, MyNodeController> = new Map();
633    @State componentIdArr: Array<string> = [];
634    @State widthMap: Map<string, number> = new Map();
635    @State heightMap: Map<string, number> = new Map();
636    @State positionMap: Map<string, Edges> = new Map();
637    @State edges: Edges = {};
638    uiContext: UIContext = this.getUIContext();
639
640    build() {
641      Row() {
642        Column() {
643          Stack() {
644            ForEach(this.componentIdArr, (componentId: string) => {
645              NodeContainer(this.nodeControllerMap.get(componentId))
646                .position(this.positionMap.get(componentId))
647                .width(this.widthMap.get(componentId))
648                .height(this.heightMap.get(componentId))
649            }, (embedId: string) => embedId)
650            // Web组件加载本地text.html页面。
651            Web({src: $rawfile("text.html"), controller: this.browserTabController})
652              // 配置同层渲染开关开启。
653              .enableNativeEmbedMode(true)
654              // 获取<embed>标签的生命周期变化数据。
655              .onNativeEmbedLifecycleChange((embed) => {
656                 console.info("NativeEmbed surfaceId" + embed.surfaceId);
657                 // 如果使用embed.info.id作为映射nodeController的key,请在h5页面显式指定id。
658                 const componentId = embed.info?.id?.toString() as string
659                 if (embed.status == NativeEmbedStatus.CREATE) {
660                   console.info("NativeEmbed create" + JSON.stringify(embed.info));
661                   // 创建节点控制器、设置参数并rebuild。
662                   let nodeController = new MyNodeController()
663                   // embed.info.widthembed.info.height单位是px格式,需要转换成ets侧的默认单位vp。
664                   nodeController.setRenderOption({surfaceId : embed.surfaceId as string,
665                     type : embed.info?.type as string,
666                     renderType : NodeRenderType.RENDER_TYPE_TEXTURE,
667                     embedId : embed.embedId as string,
668                     width : this.uiContext.px2vp(embed.info?.width),
669                     height : this.uiContext.px2vp(embed.info?.height)})
670                   this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`}
671                   nodeController.setDestroy(false);
672                   //根据web传入的embed的id属性作为key,将nodeController存入Map。
673                   this.nodeControllerMap.set(componentId, nodeController);
674                   this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width));
675                   this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height));
676                   this.positionMap.set(componentId, this.edges);
677                   // 将web传入的embed的id属性存入@State状态数组变量中,用于动态创建nodeContainer节点容器,需要将push动作放在set之后。
678                   this.componentIdArr.push(componentId)
679                 } else if (embed.status == NativeEmbedStatus.UPDATE) {
680                   let nodeController = this.nodeControllerMap.get(componentId);
681                   console.info("NativeEmbed update" + JSON.stringify(embed));
682                   this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`}
683                   this.positionMap.set(componentId, this.edges);
684                   this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width));
685                   this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height));
686                   nodeController?.updateNode({textOne: 'update', width: this.uiContext.px2vp(embed.info?.width), height: this.uiContext.px2vp(embed.info?.height)} as ESObject)
687                 } else if (embed.status == NativeEmbedStatus.DESTROY) {
688                   console.info("NativeEmbed destroy" + JSON.stringify(embed));
689                   let nodeController = this.nodeControllerMap.get(componentId);
690                   nodeController?.setDestroy(true);
691                   this.nodeControllerMap.delete(componentId);
692                   this.positionMap.delete(componentId);
693                   this.widthMap.delete(componentId);
694                   this.heightMap.delete(componentId);
695                   this.componentIdArr = this.componentIdArr.filter((value: string) => value !== componentId);
696                 } else {
697                   console.info("NativeEmbed status" + embed.status);
698                 }
699               })// 获取同层渲染组件触摸事件信息。
700              .onNativeEmbedGestureEvent((touch) => {
701                console.info("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent));
702                this.componentIdArr.forEach((componentId: string) => {
703                  let nodeController = this.nodeControllerMap.get(componentId);
704                  // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。
705                  if(nodeController?.getEmbedId() == touch.embedId) {
706                    let ret = nodeController?.postEvent(touch.touchEvent)
707                    if(ret) {
708                      console.info("onNativeEmbedGestureEvent success " + componentId);
709                    } else {
710                      console.info("onNativeEmbedGestureEvent fail " + componentId);
711                    }
712                    if(touch.result) {
713                      // 通知Web组件手势事件消费结果。
714                      touch.result.setGestureEventResult(ret);
715                    }
716                  }
717                })
718              })
719              .onNativeEmbedMouseEvent((mouse) => {
720                console.info("NativeEmbed onNativeEmbedMouseEvent" + JSON.stringify(mouse.mouseEvent));
721                this.componentIdArr.forEach((componentId: string) => {
722                  let nodeController = this.nodeControllerMap.get(componentId);
723                  // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。
724                  if(nodeController?.getEmbedId() == mouse.embedId) {
725                    let ret = nodeController?.postInputEvent(mouse.mouseEvent)
726                    if(ret) {
727                      console.info("onNativeEmbedMouseEvent success " + componentId);
728                    } else {
729                      console.info("onNativeEmbedMouseEvent fail " + componentId);
730                    }
731                    if(mouse.result) {
732                      // 通知Web组件鼠标事件消费结果。
733                      mouse.result.setMouseEventResult(ret);
734                    }
735                  }
736                })
737              })
738          }
739        }
740      }
741    }
742  }
743  ```
744
745## 绘制XComponent+AVPlayer和Button组件
746
747- 应用侧代码组件使用示例。
748
749  ```ts
750  // HAP's src/main/ets/pages/Index.ets
751  // 创建NodeController
752  import { webview } from '@kit.ArkWeb';
753  import { UIContext, NodeController, BuilderNode, NodeRenderType, FrameNode } from "@kit.ArkUI";
754  import { AVPlayerDemo } from './PlayerDemo';
755
756  @Observed
757  declare class Params {
758    textOne : string
759    textTwo : string
760    width : number
761    height : number
762  }
763
764  declare class NodeControllerParams {
765    surfaceId : string
766    type : string
767    renderType : NodeRenderType
768    embedId : string
769    width : number
770    height : number
771  }
772
773  // 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用。
774  class MyNodeController extends NodeController {
775    private rootNode: BuilderNode<[Params]> | undefined | null;
776    private embedId_ : string = "";
777    private surfaceId_ : string = "";
778    private renderType_ :NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY;
779    private width_ : number = 0;
780    private height_ : number = 0;
781    private type_ : string = "";
782    private isDestroy_ : boolean = false;
783
784    setRenderOption(params : NodeControllerParams) {
785      this.surfaceId_ = params.surfaceId;
786      this.renderType_ = params.renderType;
787      this.embedId_ = params.embedId;
788      this.width_ = params.width;
789      this.height_ = params.height;
790      this.type_ = params.type;
791    }
792    // 必须要重写的方法,用于构建节点数、返回节点数挂载在对应NodeContainer中。
793    // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新。
794    makeNode(uiContext: UIContext): FrameNode | null{
795      if (this.isDestroy_) { // rootNode为null。
796        return null;
797      }
798      if (!this.rootNode) { // rootNode 为undefined时。
799        this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_});
800        if (this.type_ === 'native/video') {
801          this.rootNode.build(wrapBuilder(VideoBuilder), {textOne: "myButton", width : this.width_, height : this.height_});
802        } else {
803          // other
804        }
805      }
806      // 返回FrameNode节点。
807      return this.rootNode.getFrameNode();
808    }
809
810    updateNode(arg: Object): void {
811      this.rootNode?.update(arg);
812    }
813    getEmbedId() : string {
814      return this.embedId_;
815    }
816
817    setDestroy(isDestroy : boolean) : void {
818      this.isDestroy_ = isDestroy;
819      if (this.isDestroy_) {
820        this.rootNode = null;
821      }
822    }
823
824    postEvent(event: TouchEvent | undefined) : boolean {
825      return this.rootNode?.postTouchEvent(event) as boolean
826    }
827
828    postInputEvent(event: MouseEvent | undefined): boolean {
829      return this.rootNode?.postInputEvent(event) as boolean
830    }
831  }
832
833  @Component
834  struct VideoComponent {
835    @ObjectLink params: Params
836    @State bkColor: Color = Color.Red
837    mXComponentController: XComponentController = new XComponentController();
838    @State player_changed: boolean = false;
839    player?: AVPlayerDemo;
840
841    build() {
842      Column() {
843        Button(this.params.textOne)
844
845        XComponent({ id: 'video_player_id', type: XComponentType.SURFACE, controller: this.mXComponentController})
846          .border({width: 1, color: Color.Red})
847          .onLoad(() => {
848            this.player = new AVPlayerDemo();
849            this.player.setSurfaceID(this.mXComponentController.getXComponentSurfaceId());
850            this.player_changed = !this.player_changed;
851            this.player.avPlayerLiveDemo()
852          })
853          .width(300)
854          .height(200)
855      }
856      //自定义组件中的最外层容器组件宽高应该为同层标签的宽高。
857      .width(this.params.width)
858      .height(this.params.height)
859    }
860  }
861  // @Builder中为动态组件的具体组件内容。
862  @Builder
863  function VideoBuilder(params: Params) {
864    VideoComponent({ params: params })
865      .backgroundColor(Color.Gray)
866  }
867
868  @Entry
869  @Component
870  struct WebIndex {
871    browserTabController: WebviewController = new webview.WebviewController()
872    private nodeControllerMap: Map<string, MyNodeController> = new Map();
873    @State componentIdArr: Array<string> = [];
874    @State widthMap: Map<string, number> = new Map();
875    @State heightMap: Map<string, number> = new Map();
876    @State positionMap: Map<string, Edges> = new Map();
877    @State edges: Edges = {};
878    uiContext: UIContext = this.getUIContext();
879
880    aboutToAppear() {
881      // 配置web开启调试模式。
882      webview.WebviewController.setWebDebuggingAccess(true);
883    }
884
885    build(){
886      Row() {
887        Column() {
888          Stack() {
889            ForEach(this.componentIdArr, (componentId: string) => {
890              NodeContainer(this.nodeControllerMap.get(componentId))
891                .position(this.positionMap.get(componentId))
892                .width(this.widthMap.get(componentId))
893                .height(this.heightMap.get(componentId))
894            }, (embedId: string) => embedId)
895            // Web组件加载本地test.html页面。
896            Web({ src: $rawfile("test.html"), controller: this.browserTabController })
897              // 配置同层渲染开关开启。
898              .enableNativeEmbedMode(true)
899                // 获取<embed>标签的生命周期变化数据。
900              .onNativeEmbedLifecycleChange((embed) => {
901                console.info("NativeEmbed surfaceId" + embed.surfaceId);
902                // 1. 如果使用embed.info.id作为映射nodeController的key,请在h5页面显式指定id。
903                const componentId = embed.info?.id?.toString() as string
904                if (embed.status == NativeEmbedStatus.CREATE) {
905                  console.info("NativeEmbed create" + JSON.stringify(embed.info))
906                  // 创建节点控制器,设置参数并rebuild。
907                  let nodeController = new MyNodeController()
908                  // 1. embed.info.widthembed.info.height单位是px格式,需要转换成ets侧的默认单位vp。
909                  nodeController.setRenderOption({surfaceId : embed.surfaceId as string, type : embed.info?.type as string,
910                    renderType : NodeRenderType.RENDER_TYPE_TEXTURE, embedId : embed.embedId as string,
911                    width : this.uiContext.px2vp(embed.info?.width), height : this.uiContext.px2vp(embed.info?.height)})
912                  this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`}
913                  nodeController.setDestroy(false);
914                  // 根据web传入的embed的id属性作为key,将nodeController存入map。
915                  this.nodeControllerMap.set(componentId, nodeController)
916                  this.widthMap.set(componentId,  this.uiContext.px2vp(embed.info?.width));
917                  this.heightMap.set(componentId,  this.uiContext.px2vp(embed.info?.height));
918                  this.positionMap.set(componentId, this.edges);
919                  // 将web传入的embed的id属性存入@State状态数组变量中,用于动态创建nodeContainer节点容器,需要将push动作放在set之后。
920                  this.componentIdArr.push(componentId)
921                } else if (embed.status == NativeEmbedStatus.UPDATE) {
922                  let nodeController = this.nodeControllerMap.get(componentId)
923                  this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`}
924                  this.positionMap.set(componentId, this.edges);
925                  this.widthMap.set(componentId,  this.uiContext.px2vp(embed.info?.width));
926                  this.heightMap.set(componentId,  this.uiContext.px2vp(embed.info?.height));
927                  nodeController?.updateNode({textOne: 'update', width: this.uiContext.px2vp(embed.info?.width), height: this.uiContext.px2vp(embed.info?.height)} as ESObject)
928                } else if (embed.status == NativeEmbedStatus.DESTROY) {
929                  let nodeController = this.nodeControllerMap.get(componentId);
930                  nodeController?.setDestroy(true);
931                  this.nodeControllerMap.delete(componentId);
932                  this.positionMap.delete(componentId);
933                  this.widthMap.delete(componentId);
934                  this.heightMap.delete(componentId);
935                  this.componentIdArr = this.componentIdArr.filter((value: string) => value !== componentId);
936                } else {
937                  console.info("NativeEmbed status" + embed.status);
938                }
939              })// 获取同层渲染组件触摸事件信息。
940              .onNativeEmbedGestureEvent((touch) => {
941                console.info("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent));
942                this.componentIdArr.forEach((componentId: string) => {
943                  let nodeController = this.nodeControllerMap.get(componentId)
944                  // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。
945                  if (nodeController?.getEmbedId() === touch.embedId) {
946                    let ret = nodeController?.postEvent(touch.touchEvent)
947                    if (ret) {
948                      console.info("onNativeEmbedGestureEvent success " + componentId)
949                    } else {
950                      console.info("onNativeEmbedGestureEvent fail " + componentId)
951                    }
952                    if (touch.result) {
953                      // 通知Web组件手势事件消费结果。
954                      touch.result.setGestureEventResult(ret);
955                    }
956                  }
957                })
958              })
959              .onNativeEmbedMouseEvent((mouse) => {
960                console.info("NativeEmbed onNativeEmbedMouseEvent" + JSON.stringify(mouse.mouseEvent));
961                this.componentIdArr.forEach((componentId: string) => {
962                  let nodeController = this.nodeControllerMap.get(componentId);
963                  // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。
964                  if(nodeController?.getEmbedId() == mouse.embedId) {
965                    let ret = nodeController?.postInputEvent(mouse.mouseEvent)
966                    if(ret) {
967                      console.info("onNativeEmbedMouseEvent success " + componentId);
968                    } else {
969                      console.info("onNativeEmbedMouseEvent fail " + componentId);
970                    }
971                    if(mouse.result) {
972                      // 通知Web组件鼠标事件消费结果。
973                      mouse.result.setMouseEventResult(ret);
974                    }
975                  }
976                })
977              })
978          }
979        }
980      }
981    }
982  }
983  ```
984
985- 应用侧代码示例,视频播放,使用时需替换为正确的视频链接地址。
986
987  ```ts
988  // HAP's src/main/ets/pages/PlayerDemo.ets
989  import { media } from '@kit.MediaKit';
990  import { BusinessError } from '@ohos.base';
991
992  export class AVPlayerDemo {
993    private count: number = 0;
994    private surfaceID: string = ''; // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法。
995    private isSeek: boolean = true; // 用于区分模式是否支持seek操作。
996
997    setSurfaceID(surface_id: string){
998      console.info('setSurfaceID : ' + surface_id);
999      this.surfaceID = surface_id;
1000    }
1001    // 注册avplayer回调函数。
1002    setAVPlayerCallback(avPlayer: media.AVPlayer) {
1003      // seek操作结果回调函数。
1004      avPlayer.on('seekDone', (seekDoneTime: number) => {
1005        console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
1006      })
1007      // error回调监听函数,当avplayer在操作过程中出现错误时,调用reset接口触发重置流程。
1008      avPlayer.on('error', (err: BusinessError) => {
1009        console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
1010        avPlayer.reset();
1011      })
1012      // 状态机变化回调函数。
1013      avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => {
1014        switch (state) {
1015          case 'idle': // 成功调用reset接口后触发该状态机上报。
1016            console.info('AVPlayer state idle called.');
1017            avPlayer.release(); // 调用release接口销毁实例对象。
1018            break;
1019          case 'initialized': // avplayer 设置播放源后触发该状态上报。
1020            console.info('AVPlayer state initialized called.');
1021            avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置。
1022            avPlayer.prepare();
1023            break;
1024          case 'prepared': // prepared调用成功后上报该状态机。
1025            console.info('AVPlayer state prepared called.');
1026            avPlayer.play(); // 调用播放接口开始播放。
1027            break;
1028          case 'playing': // play成功调用后触发该状态机上报。
1029            console.info('AVPlayer state playing called.');
1030            if(this.count !== 0) {
1031              if (this.isSeek) {
1032                console.info('AVPlayer start to seek.');
1033                avPlayer.seek(avPlayer.duration); // seek到视频末尾。
1034              } else {
1035                // 当播放模式不支持seek操作时继续播放到结尾。
1036                console.info('AVPlayer wait to play end.');
1037              }
1038            } else {
1039              avPlayer.pause(); // 调用暂停接口暂停播放。
1040            }
1041            this.count++;
1042            break;
1043          case 'paused': // pause成功调用后触发该状态机上报。
1044            console.info('AVPlayer state paused called.');
1045            avPlayer.play(); // 再次播放接口开始播放。
1046            break;
1047          case 'completed': //播放接口后触发该状态机上报。
1048            console.info('AVPlayer state completed called.');
1049            avPlayer.stop(); // 调用播放接口。
1050            break;
1051          case 'stopped': // stop接口后触发该状态机上报。
1052            console.info('AVPlayer state stopped called.');
1053            avPlayer.reset(); // 调用reset接口初始化avplayer状态。
1054            break;
1055          case 'released': //播放接口后触发该状态机上报。
1056            console.info('AVPlayer state released called.');
1057            break;
1058          default:
1059            break;
1060        }
1061      })
1062    }
1063
1064    // 通过url设置网络地址来实现播放直播码流。
1065    async avPlayerLiveDemo(){
1066      // 创建avPlayer实例对象。
1067      let avPlayer: media.AVPlayer = await media.createAVPlayer();
1068      // 创建状态机变化回调函数。
1069      this.setAVPlayerCallback(avPlayer);
1070      this.isSeek = false; // 不支持seek操作。
1071      // 使用时需要自行替换视频链接。
1072      avPlayer.url = 'https://xxx.xxx/demo.mp4';
1073    }
1074  }
1075  ```
1076
1077- 前端页面示例。
1078
1079  ```html
1080  <!--HAP's src/main/resources/rawfile/test.html-->
1081  <!DOCTYPE html>
1082  <html>
1083  <head>
1084      <title>同层渲染测试html</title>
1085      <meta name="viewport">
1086  </head>
1087  <body>
1088  <div>
1089      <div id="bodyId">
1090          <embed id="nativeVideo" type = "native/video" width="1000" height="1500" src="test" style = "background-color:red"/>
1091      </div>
1092  </div>
1093  </body>
1094  </html>
1095  ```
1096
1097- 实现效果:
1098
1099  ![web-same-layer](figures/web-same-layer.png)
1100
1101## 同层标签设置为最高层级
1102
1103同层渲染支持私有属性arkwebnativestyle,该属性仅在开启同层渲染后的&lt;embed&gt;和&lt;object&gt;中生效,该属性的display属性用于控制同层标签的显示层级,使其高于其他Web元素。如果多个同层标签都设置了arkwebnativestyle的display属性,并且属性相同,则它们的层级顺序将遵循W3C标准层级排序规则:先比较z-index属性值,当z-index相同时,按照元素在DOM中的先后顺序排序。display属性取值说明如下:
1104
1105| display取值 | 说明 |
1106| - | - |
1107| overlay | 设置同层标签层级高于其他Web元素。 |
1108| overlay-infinity | 设置同层标签层级高于其他Web元素和设置overlay的同层标签。 |
1109
1110- 应用侧代码:
1111  ```ts
1112  import { webview } from '@kit.ArkWeb';
1113  import { UIContext } from '@kit.ArkUI';
1114  import { NodeController, BuilderNode, NodeRenderType, FrameNode } from '@kit.ArkUI';
1115
1116  @Observed
1117  declare class Params{
1118    elementId: string
1119    textOne: string
1120    textTwo: string
1121    width: number
1122    height: number
1123  }
1124
1125  declare class NodeControllerParams {
1126    surfaceId: string
1127    type: string
1128    renderType: NodeRenderType
1129    embedId: string
1130    width: number
1131    height: number
1132  }
1133
1134  // 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用。
1135  class MyNodeController extends NodeController {
1136    private rootNode: BuilderNode<[Params]> | undefined | null;
1137    private embedId_: string = "";
1138    private surfaceId_: string = "";
1139    private renderType_: NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY;
1140    private width_: number = 0;
1141    private height_: number = 0;
1142    private type_: string = "";
1143    private isDestroy_: boolean = false;
1144
1145    setRenderOption(params: NodeControllerParams) {
1146      this.surfaceId_ = params.surfaceId;
1147      this.renderType_ = params.renderType;
1148      this.embedId_ = params.embedId;
1149      this.width_ = params.width;
1150      this.height_ = params.height;
1151      this.type_ = params.type;
1152    }
1153
1154    // 必须要重写的方法,用于构建节点数、返回节点数挂载在对应NodeContainer中。
1155    // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新。
1156    makeNode(uiContext: UIContext): FrameNode | null {
1157      if (this.isDestroy_) { // rootNode为null。
1158        return null;
1159      }
1160      if (!this.rootNode) {// rootNode 为undefined时。
1161        this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_ });
1162        if (this.type_ == 'native/view1') {
1163          this.rootNode.build(wrapBuilder(TextInputBuilder1), {  textOne: "myTextInput", width: this.width_, height: this.height_  })
1164          return this.rootNode.getFrameNode();
1165        } else if (this.type_ == 'native/view2') {
1166          this.rootNode.build(wrapBuilder(TextInputBuilder2), {  textOne: "myTextInput", width: this.width_, height: this.height_  })
1167          return this.rootNode.getFrameNode();
1168        } else{
1169          return null;
1170        }
1171      }
1172      // 返回FrameNode节点。
1173      return this.rootNode.getFrameNode();
1174    }
1175
1176    updateNode(arg: Object): void {
1177      this.rootNode?.update(arg);
1178    }
1179
1180    getEmbedId(): string {
1181      return this.embedId_;
1182    }
1183
1184    setDestroy(isDestroy: boolean): void {
1185      this.isDestroy_ = isDestroy;
1186      if (this.isDestroy_) {
1187        this.rootNode = null;
1188      }
1189    }
1190
1191    postEvent(event: TouchEvent | undefined): boolean {
1192      return this.rootNode?.postTouchEvent(event) as boolean
1193    }
1194  }
1195
1196  @Component
1197  struct TextInputComponent1 {
1198    @Prop params: Params;
1199    @State bkColor: Color = Color.White;
1200
1201    build() {
1202      Column() {
1203        Text("display:overlay-infinity")
1204        TextInput({text: '', placeholder: 'please input your word...'})
1205          .placeholderColor(Color.Gray)
1206          .id(this.params?.elementId)
1207          .placeholderFont({size: 13, weight: 400})
1208          .caretColor(Color.Gray)
1209          .fontSize(14)
1210          .fontColor(Color.Black)
1211      }
1212      // 自定义组件中的最外层容器组件宽高应该为同层标签的宽高。
1213      .width(this.params.width)
1214      .height(this.params.height)
1215    }
1216  }
1217
1218  // @Builder中为动态组件的具体组件内容。
1219  @Builder
1220  function TextInputBuilder1(params:Params) {
1221    TextInputComponent1({params: params})
1222      .width(params.width)
1223      .height(params.height)
1224      .backgroundColor(Color.Pink)
1225  }
1226
1227  @Component
1228  struct TextInputComponent2 {
1229    @Prop params: Params;
1230    @State bkColor: Color = Color.White;
1231
1232    build() {
1233      Column() {
1234        Text("display:overlay")
1235        TextInput({text: '', placeholder: 'please input your word...'})
1236          .placeholderColor(Color.Gray)
1237          .id(this.params?.elementId)
1238          .placeholderFont({size: 13, weight: 400})
1239          .caretColor(Color.Gray)
1240          .fontSize(14)
1241          .fontColor(Color.Black)
1242      }
1243      // 自定义组件中的最外层容器组件宽高应该为同层标签的宽高。
1244      .width(this.params.width)
1245      .height(this.params.height)
1246    }
1247  }
1248
1249
1250  // @Builder中为动态组件的具体组件内容。
1251  @Builder
1252  function TextInputBuilder2(params:Params) {
1253    TextInputComponent2({params: params})
1254      .width(params.width)
1255      .height(params.height)
1256      .backgroundColor(Color.Gray)
1257  }
1258
1259  @Entry
1260  @Component
1261  struct Page{
1262    browserTabController: webview.WebviewController = new webview.WebviewController();
1263    private nodeControllerMap: Map<string, MyNodeController> = new Map();
1264    @State componentIdArr: Array<string> = [];
1265    @State widthMap: Map<string, number> = new Map();
1266    @State heightMap: Map<string, number> = new Map();
1267    @State positionMap: Map<string, Edges> = new Map();
1268    @State edges: Edges = {};
1269    uiContext: UIContext = this.getUIContext();
1270
1271    build() {
1272      Row() {
1273        Column() {
1274          Stack() {
1275            ForEach(this.componentIdArr, (componentId: string) => {
1276              NodeContainer(this.nodeControllerMap.get(componentId))
1277                .position(this.positionMap.get(componentId))
1278                .width(this.widthMap.get(componentId))
1279                .height(this.heightMap.get(componentId))
1280            }, (embedId: string) => embedId)
1281            // Web组件加载本地text.html页面。
1282            Web({src: $rawfile("overlay.html"), controller: this.browserTabController})
1283              // 配置同层渲染开关开启。
1284              .enableNativeEmbedMode(true)
1285                // 获取<embed>标签的生命周期变化数据。
1286              .onNativeEmbedLifecycleChange((embed) => {
1287                console.info("NativeEmbed surfaceId" + embed.surfaceId);
1288                // 如果使用embed.info.id作为映射nodeController的key,请在h5页面显式指定id。
1289                const componentId = embed.info?.id?.toString() as string
1290                if (embed.status == NativeEmbedStatus.CREATE) {
1291                  console.info("NativeEmbed create" + JSON.stringify(embed.info));
1292                  // 创建节点控制器、设置参数并rebuild。
1293                  let nodeController = new MyNodeController()
1294                  // embed.info.widthembed.info.height单位是px格式,需要转换成ets侧的默认单位vp。
1295                  nodeController.setRenderOption({surfaceId : embed.surfaceId as string,
1296                    type : embed.info?.type as string,
1297                    renderType : NodeRenderType.RENDER_TYPE_TEXTURE,
1298                    embedId : embed.embedId as string,
1299                    width : this.uiContext.px2vp(embed.info?.width),
1300                    height : this.uiContext.px2vp(embed.info?.height)})
1301                  this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`}
1302                  nodeController.setDestroy(false);
1303                  // 根据web传入的embed的id属性作为key,将nodeController存入Map。
1304                  this.nodeControllerMap.set(componentId, nodeController);
1305                  this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width));
1306                  this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height));
1307                  this.positionMap.set(componentId, this.edges);
1308                  // 将web传入的embed的id属性存入@State状态数组变量中,用于动态创建nodeContainer节点容器,需要将push动作放在set之后。
1309                  this.componentIdArr.push(componentId)
1310                } else if (embed.status == NativeEmbedStatus.UPDATE) {
1311                  let nodeController = this.nodeControllerMap.get(componentId);
1312                  console.info("NativeEmbed update" + JSON.stringify(embed));
1313                  this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`}
1314                  this.positionMap.set(componentId, this.edges);
1315                  this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width));
1316                  this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height));
1317                  nodeController?.updateNode({textOne: 'update', width: this.uiContext.px2vp(embed.info?.width), height: this.uiContext.px2vp(embed.info?.height)} as ESObject)
1318                } else if (embed.status == NativeEmbedStatus.DESTROY) {
1319                  console.info("NativeEmbed destroy" + JSON.stringify(embed));
1320                  let nodeController = this.nodeControllerMap.get(componentId);
1321                  nodeController?.setDestroy(true);
1322                  this.nodeControllerMap.delete(componentId);
1323                  this.positionMap.delete(componentId);
1324                  this.widthMap.delete(componentId);
1325                  this.heightMap.delete(componentId);
1326                  this.componentIdArr = this.componentIdArr.filter((value: string) => value !== componentId);
1327                } else {
1328                  console.info("NativeEmbed status" + embed.status);
1329                }
1330              })// 获取同层渲染组件触摸事件信息。
1331              .onNativeEmbedGestureEvent((touch) => {
1332                console.info("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent));
1333                this.componentIdArr.forEach((componentId: string) => {
1334                  let nodeController = this.nodeControllerMap.get(componentId);
1335                  // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。
1336                  if(nodeController?.getEmbedId() == touch.embedId) {
1337                    let ret = nodeController?.postEvent(touch.touchEvent)
1338                    if(ret) {
1339                      console.info("onNativeEmbedGestureEvent success " + componentId);
1340                    } else {
1341                      console.info("onNativeEmbedGestureEvent fail " + componentId);
1342                    }
1343                    if(touch.result) {
1344                      // 通知Web组件手势事件消费结果。
1345                      touch.result.setGestureEventResult(ret);
1346                    }
1347                  }
1348                })
1349              })
1350              .border({width: 2, color: Color.Gray})
1351              .height("50%")
1352          }
1353        }
1354      }
1355    }
1356  }
1357  ```
1358
1359- 前端页面示例:
1360
1361  示例代码使用&lt;embed&gt;标签,若使用&lt;object&gt;标签,请在ets侧注册&lt;object&gt;标签及type类型。
1362  ```html
1363  <!--HAP's src/main/resources/rawfile/overlay.html-->
1364  <!DOCTYPE html>
1365  <html>
1366  <head>
1367      <title>同层渲染html</title>
1368      <meta name="viewport" content="initial-scale=1.0">
1369  </head>
1370  <body>
1371  <div>
1372      <div id = "test" style = "position: absolute; z-index: 9999; text-align: center; background-color: rgb(61, 157, 180); top: 40px; left: 30px; width: 300px; height: 120px">
1373          z-index: 9999
1374      </div>
1375
1376      <embed id = "input1" type = "native/view1" arkwebnativestyle = "display:overlay-infinity" style = "position: absolute; top: 60px; left: 50px; width: 300px; height: 100px">
1377
1378      <embed id = "input2" type = "native/view2" arkwebnativestyle = "display:overlay" style = "position: absolute; top: 150px; left: 40px; width: 300px; height: 100px">
1379  </div>
1380  </body>
1381  </html>
1382  ```
1383
1384- 实现效果:
1385
1386  未设置arkwebnativestyle的display属性:
1387
1388  ![web-same-layer](figures/web-same-layer-nativeEmbedOverlay1.png)
1389
1390  设置arkwebnativestyle的display属性:
1391
1392  ![web-same-layer](figures/web-same-layer-nativeEmbedOverlay2.png)
1393
1394## 常见问题
1395### 同层渲染组件被拉伸该如何解决?
1396
1397- 组件高度过大
1398
1399  受GPU限制,同层标签存在8000px的高度限制,如果html5中同层标签高度过高,会存在组件被拉伸的情况,这时需要将同层标签的高度设为8000px以下。
1400
1401- 自定义组件宽高未指定为同层渲染标签的宽高
1402
1403  自定义的同层渲染组件宽高需要与同层标签的宽高保持一致,示例如下:
1404  ```ts
1405    @Component
1406    struct TextInputComponent {
1407      @Prop params: Params
1408      @State bkColor: Color = Color.White
1409
1410      build() {
1411        Column() {
1412          TextInput({text: '', placeholder: 'please input your word...'})
1413            .fontColor(Color.Black)
1414        }
1415        // 自定义组件中的最外层容器组件宽高应该为同层标签的宽高。
1416        .width(this.params.width)
1417        .height(this.params.height)
1418      }
1419    }
1420  ```
1421
1422### 如何将同层渲染组件捕获到的事件透传到web前端?
1423同层渲染手势事件通过[setGestureEventResult()](../reference/apis-arkweb/arkts-basic-components-web-EventResult.md#setgestureeventresult14)设置手势事件消费结果,可以选择系统组件侧或ArkWeb侧消费手势事件。如果要实现系统组件侧和ArkWeb侧同时消费手势事件,可以在[setGestureEventResult()](../reference/apis-arkweb/arkts-basic-components-web-EventResult.md#setgestureeventresult14)中将stopPropagation设置为false,即系统组件侧消费的同时可以将手势事件向上冒泡给ArkWeb。
1424
1425### 同层渲染页面显示该插件不支持该如何解决?
1426
1427- 同层渲染开关[enableNativeEmbedMode](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#enablenativeembedmode11)未开启
1428
1429  使用同层渲染技术需要显式开启同层渲染开关
1430  ```ts
1431  Web({ src: $rawfile("text.html"), controller: this.controller })
1432    // 配置同层渲染开关开启。
1433    .enableNativeEmbedMode(true)
1434  ```
1435
1436- 同层标签使用有误
1437
1438  如果使用&lt;embed&gt;标签,需要显式书写embed,并且type类型以"native/"开头;如果使用&lt;object&gt;标签,需要注册&lt;object&gt;标签及type类型。
1439
1440### 涉及界面交互的ArkUI组件(如TextInput等)光标与输入框错位该如何解决?
1441首先,需使用Stack包裹同层组件容器和BuilderNode。其次,同层组件容器NodeContainer应与同层标签的位置绑定。示例如下:
1442```ts
1443ForEach(this.componentIdArr, (componentId: string) => {
1444  NodeContainer(this.nodeControllerMap.get(componentId))
1445    // 同层组件容器应与同层标签的宽高和位置绑定。
1446    .position(this.positionMap.get(componentId))
1447    .width(this.widthMap.get(componentId))
1448    .height(this.heightMap.get(componentId))
1449}, (embedId: string) => embedId)
1450```