1# Web组件在不同的窗口间迁移 2<!--Kit: ArkWeb--> 3<!--Subsystem: Web--> 4<!--Owner: @weixin_41848015--> 5<!--Designer: @libing23232323--> 6<!--Tester: @ghiker--> 7<!--Adviser: @HelloCrease--> 8 9Web组件能够实现在不同窗口的组件树上进行挂载或移除操作,这一能力使得开发者可以将同一个Web组件在不同窗口间迁移。例如,将浏览器的Tab页拖出成独立窗口,或拖入浏览器的另一个窗口。 10 11Web组件在不同窗口间迁移,是基于[自定义节点](../ui/arkts-user-defined-node.md)能力实现的。实现的基本原理是:通过[BuilderNode](../ui/arkts-user-defined-arktsNode-builderNode.md),开发者可创建Web组件的离线节点,并结合[自定义占位节点](../ui/arkts-user-defined-place-holder.md)控制Web节点的挂载与移除。当从一个窗口上移除Web节点,并挂载到另一个窗口中,即完成Web组件在窗口间的迁移。 12 13在以下示例中,主窗Ability启动时,通过命令式的方式创建了一个Web组件。开发者可以利用common.ets中提供的方法和类,实现Web组件的挂载和移除。Index.ets则提供了一种挂载和移除Web组件的实现方法。通过这种方式,开发者能够实现Web组件在不同窗口中页面的挂载与移除,即实现了Web组件在不同窗口间的迁移。下图是展示了这一迁移过程的示意图。 14 15 16 17> **说明:** 18> 19> 不要将一个Web组件同时挂载在两个父节点下,这会导致非预期行为。 20 21```ts 22// 主窗Ability 23// EntryAbility.ets 24import { createNWeb, defaultUrl } from '../pages/common' 25 26// ... 27 28 onWindowStageCreate(windowStage: window.WindowStage): void { 29 hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); 30 31 windowStage.loadContent('pages/Index', (err) => { 32 if (err && err.code) { 33 hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 34 return; 35 } 36 // 创建Web动态组件(需传入UIContext),loadContent之后的任意时机均可创建,应用仅创建一个Web组件 37 createNWeb(defaultUrl, windowStage.getMainWindowSync().getUIContext()); 38 hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); 39 }); 40 } 41 42// ... 43``` 44 45```ts 46// 提供动态挂载Web组件能力 47// pages/common.ets 48import { UIContext, NodeController, BuilderNode, FrameNode } from '@kit.ArkUI'; 49import { webview } from '@kit.ArkWeb'; 50import { hilog } from '@kit.PerformanceAnalysisKit'; 51 52export const defaultUrl : string = 'https://www.example.com'; 53 54// Data为入参封装类 55class Data{ 56 url: string = ''; 57 webController: webview.WebviewController | null = null; 58 59 constructor(url: string, webController: webview.WebviewController) { 60 this.url = url; 61 this.webController = webController; 62 } 63} 64 65// @Builder中为动态组件的具体组件内容 66@Builder 67function WebBuilder(data:Data) { 68 Web({ src: data.url, controller: data.webController }) 69 .width("100%") 70 .height("100%") 71 .borderStyle(BorderStyle.Dashed) 72 .borderWidth(2) 73} 74 75let wrap = wrapBuilder<[Data]>(WebBuilder); 76 77// 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用 78export class MyNodeController extends NodeController { 79 private builderNode: BuilderNode<[Data]> | null | undefined = null; 80 private webController : webview.WebviewController | null | undefined = null; 81 private rootNode : FrameNode | null = null; 82 83 constructor(builderNode : BuilderNode<[Data]> | undefined, webController : webview.WebviewController | undefined) { 84 super(); 85 this.builderNode = builderNode; 86 this.webController = webController; 87 } 88 89 // 必须要重写的方法,用于构建节点数、返回节点挂载在对应NodeContainer中 90 // 在对应NodeContainer创建的时候调用或者通过rebuild方法调用刷新 91 makeNode(uiContext: UIContext): FrameNode | null { 92 // 该节点会被挂载在NodeContainer的父节点下 93 return this.rootNode; 94 } 95 96 // 挂载Webview 97 attachWeb() : void { 98 if (this.builderNode) { 99 let frameNode : FrameNode | null = this.builderNode.getFrameNode(); 100 if (frameNode?.getParent() != null) { 101 // 挂载自定义节点前判断该节点是否已经被挂载 102 hilog.error(0x0000, 'testTag', '%{public}s', 'The frameNode is already attached'); 103 return; 104 } 105 this.rootNode = this.builderNode.getFrameNode(); 106 } 107 } 108 109 // 卸载Webview 110 detachWeb() : void { 111 this.rootNode = null; 112 } 113 114 getWebController() : webview.WebviewController | null | undefined { 115 return this.webController; 116 } 117} 118 119// 创建Map保存所需要的BuilderNode 120let builderNodeMap : Map<string, BuilderNode<[Data]> | undefined> = new Map(); 121// 创建Map保存所需要的webview.WebviewController 122let webControllerMap : Map<string, webview.WebviewController | undefined> = new Map(); 123 124// 初始化需要UIContext对象,UIContext对象可通过窗口或自定义组件的getUIContext方法获取 125export const createNWeb = (url: string, uiContext: UIContext) => { 126 // 创建WebviewController 127 let webController = new webview.WebviewController() ; 128 // 创建BuilderNode 129 let builderNode : BuilderNode<[Data]> = new BuilderNode(uiContext); 130 // 创建动态Web组件 131 builderNode.build(wrap, new Data(url, webController)); 132 133 // 保存BuilderNode 134 builderNodeMap.set(url, builderNode); 135 // 保存WebviewController 136 webControllerMap.set(url, webController); 137} 138 139// 自定义获取BuilderNode的接口 140export const getBuilderNode = (url : string) : BuilderNode<[Data]> | undefined => { 141 return builderNodeMap.get(url); 142} 143// 自定义获取WebviewController的接口 144export const getWebviewController = (url : string) : webview.WebviewController | undefined => { 145 return webControllerMap.get(url); 146} 147 148``` 149 150```ts 151// 使用NodeController的Page页 152// pages/Index.ets 153import { getBuilderNode, MyNodeController, defaultUrl, getWebviewController } from "./common" 154 155@Entry 156@Component 157struct Index { 158 private nodeController : MyNodeController = 159 new MyNodeController(getBuilderNode(defaultUrl), getWebviewController(defaultUrl)); 160 161 build() { 162 Row() { 163 Column() { 164 Button("Attach Webview") 165 .onClick(() => { 166 // 注意不要将同一个节点同时挂载在不同的页面上! 167 this.nodeController.attachWeb(); 168 this.nodeController.rebuild(); 169 }) 170 Button("Detach Webview") 171 .onClick(() => { 172 this.nodeController.detachWeb(); 173 this.nodeController.rebuild(); 174 }) 175 // NodeContainer用于与NodeController节点绑定,rebuild会触发makeNode 176 // Page页通过NodeContainer接口绑定NodeController,实现动态组件页面显示 177 NodeContainer(this.nodeController) 178 .height("80%") 179 .width("80%") 180 } 181 .width('100%') 182 } 183 .height('100%') 184 } 185} 186 187``` 188 189