1# Using Offline Web Components 2 3The **Web** component can be attached to and detached from the component trees in different windows. With this capability, you can create **Web** components in advance to optimize performance. For example, when a tab page is implemented with a **Web** component, pre-creation of the **Web** component allows for ahead-of-time rendering, so that the page appears instantly when accessed. 4 5The offline **Web** component is created based on the custom placeholder component [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md). The basic principle is as follows: **Web** components that are created using commands are not attached to the component tree immediately after being created. This means they are not displayed to users immediately, remaining in the **Hidden** or **InActive** state until explicitly attached. You can dynamically attach these components as required to implement more flexible usage. 6 7Offline **Web** components can be used to pre-start the rendering process and pre-render web pages. 8 9- Pre-start rendering process: By creating an empty **Web** component prior to the user's access of the web page, the rendering process is initiated in advance, preparing for subsequent use of the page. 10- Pre-render web pages: In the web page startup or redirection scenario, creating **Web** components in the background in advance allows for ahead-of-time data loading and rendering. In this way, web pages can be instantly displayed when being redirected to. 11 12## Overall Architecture 13 14As shown in the following figure, to create an offline **Web** component, you need to define a custom stateless [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md) to encapsulate the **Web** component, and bind the component to the corresponding [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md). To display a **Web** component that has been pre-rendered in the background, use [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md) to attach the component to [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md) of ViewTree. 15 16 17 18## Creating an Offline Web Component 19 20This example shows how to create an offline **Web** component in advance and attach it to the component tree for display when necessary. Such an offline **Web** component can be used in pre-starting the rendering process and pre-rendering the web page to optimize the performance. 21 22> **NOTE** 23> 24> When creating **Web** components, be mindful that each component consumes a significant amount of memory (about 200 MB) and computing resources. To optimize resource usage, limit the number of offline **Web** components created at a time. 25 26```ts 27// Carrier ability 28// EntryAbility.ets 29import { createNWeb } from "../pages/common" 30onWindowStageCreate(windowStage: window.WindowStage): void { 31 windowStage.loadContent('pages/Index', (err, data) => { 32 // Create a dynamic Web component, in which the UIContext should be passed. The component can be created at any time after loadContent() is called. 33 createNWeb("https://www.example.com", windowStage.getMainWindowSync().getUIContext()); 34 if (err.code) { 35 return; 36 } 37 }); 38} 39``` 40 41```ts 42// Create a NodeController instance. 43// common.ets 44import { UIContext, NodeController, BuilderNode, Size, FrameNode } from '@kit.ArkUI'; 45import { webview } from '@kit.ArkWeb'; 46 47// @Builder contains the specific information of the dynamic component. 48// Data is an input parameter of encapsulation class. 49class Data{ 50 url: ResourceStr = "https://www.example.com"; 51 controller: WebviewController = new webview.WebviewController(); 52} 53 54@Builder 55function WebBuilder(data:Data) { 56 Column() { 57 Web({ src: data.url, controller: data.controller }) 58 .width("100%") 59 .height("100%") 60 } 61} 62 63let wrap = wrapBuilder<Data[]>(WebBuilder); 64 65// Used to control and report the behavior of the node in NodeContainer. This function must be used together with NodeContainer. 66export class myNodeController extends NodeController { 67 private rootnode: BuilderNode<Data[]> | null = null; 68 // This function must be overridden, which is used to construct the number of nodes, return the nodes and attach them to NodeContainer. 69 // Call it when the NodeContainer is created or call rebuild() to refresh. 70 makeNode(uiContext: UIContext): FrameNode | null { 71 console.log(" uicontext is undefined : "+ (uiContext === undefined)); 72 if (this.rootnode != null) { 73 // Return the FrameNode. 74 return this.rootnode.getFrameNode(); 75 } 76 // Return null to detach the dynamic component from the bound node. 77 return null; 78 } 79 // Called when the layout size changes. 80 aboutToResize(size: Size) { 81 console.log("aboutToResize width : " + size.width + " height : " + size.height ); 82 } 83 84 // Called when the NodeContainer bound to the controller is about to appear. 85 aboutToAppear() { 86 console.log("aboutToAppear"); 87 } 88 89 // Called when the NodeContainer bound to the controller is about to disappear. 90 aboutToDisappear() { 91 console.log("aboutToDisappear"); 92 } 93 94 // This function is a custom function and can be used as an initialization function. 95 // Initialize BuilderNode through UIContext, and then initialize the content in @Builder through the build API in BuilderNode. 96 initWeb(url:ResourceStr, uiContext:UIContext, control:WebviewController) { 97 if(this.rootnode != null) 98 { 99 return; 100 } 101 // Create a node, during which the UIContext should be passed. 102 this.rootnode = new BuilderNode(uiContext); 103 // Create a dynamic Web component. 104 this.rootnode.build(wrap, { url:url, controller:control }); 105 } 106} 107// Create a Map to save the required NodeController. 108let NodeMap:Map<ResourceStr, myNodeController | undefined> = new Map(); 109// Create a Map to save the required WebViewController. 110let controllerMap:Map<ResourceStr, WebviewController | undefined> = new Map(); 111 112// UIContext is required for initialization and needs to be obtained from the ability. 113export const createNWeb = (url: ResourceStr, uiContext: UIContext) => { 114 // Create a NodeController instance. 115 let baseNode = new myNodeController(); 116 let controller = new webview.WebviewController() ; 117 // Initialize the custom Web component. 118 baseNode.initWeb(url, uiContext, controller); 119 controllerMap.set(url, controller) 120 NodeMap.set(url, baseNode); 121} 122// Customize the API for obtaining the NodeController. 123export const getNWeb = (url: ResourceStr) : myNodeController | undefined => { 124 return NodeMap.get(url); 125} 126``` 127 128```ts 129// Use the pages of NodeController. 130// Index.ets 131import { getNWeb } from "./common" 132@Entry 133@Component 134struct Index { 135 build() { 136 Row() { 137 Column() { 138 // NodeContainer is used to bind to the NodeController. A rebuild call triggers makeNode. 139 // The Page page is bound to the NodeController through the NodeContainer API to display the dynamic component. 140 NodeContainer(getNWeb("https://www.example.com")) 141 .height("90%") 142 .width("100%") 143 } 144 .width('100%') 145 } 146 .height('100%') 147 } 148} 149``` 150 151## Pre-starting the Web Rendering Process 152 153To save time required for starting the web rendering process when the **Web** component is loaded, you can create a **Web** component in the background in advance. 154 155> **NOTE** 156> 157> The optimization effect is obvious only when the single-rendering-process mode is used, that is, one web rendering process is globally shared. The web rendering process is terminated when all **Web** components are destroyed. Therefore, you are advised to keep at least one **Web** component active. 158 159In the following example, a **Web** component is pre-created during **onWindowStageCreate** phase to load a blank page. In this way, the rendering process is started in advance. When the index is redirected to index2, the time required for starting and initializing the rendering process of the Web component is reduced. 160 161Creating additional **Web** components causes memory overhead. Therefore, you are advised to reuse the **Web** components based on this solution. 162 163```ts 164// Carrier ability 165// EntryAbility.ets 166import { createNWeb } from "../pages/common" 167onWindowStageCreate(windowStage: window.WindowStage): void { 168 windowStage.loadContent('pages/Index', (err, data) => { 169 // Create an empty dynamic Web component, in which the UIContext should be passed. The component can be created at any time after loadContent() is called. 170 createNWeb("about: blank", windowStage.getMainWindowSync().getUIContext()); 171 if (err.code) { 172 return; 173 } 174 }); 175} 176``` 177 178```ts 179// Create a NodeController instance. 180// common.ets 181import { UIContext, NodeController, BuilderNode, Size, FrameNode } from '@kit.ArkUI'; 182import { webview } from '@kit.ArkWeb'; 183 184// @Builder contains the specific information of the dynamic component. 185// Data is an input parameter of encapsulation class. 186class Data{ 187 url: ResourceStr = "https://www.example.com"; 188 controller: WebviewController = new webview.WebviewController(); 189} 190 191@Builder 192function WebBuilder(data:Data) { 193 Column() { 194 Web({ src: data.url, controller: data.controller }) 195 .width("100%") 196 .height("100%") 197 } 198} 199 200let wrap = wrapBuilder<Data[]>(WebBuilder); 201 202// Used to control and report the behavior of the node in NodeContainer. This function must be used together with NodeContainer. 203export class myNodeController extends NodeController { 204 private rootnode: BuilderNode<Data[]> | null = null; 205 // This function must be overridden, which is used to construct the number of nodes, return the nodes and attach them to NodeContainer. 206 // Call it when the NodeContainer is created or call rebuild() to refresh. 207 makeNode(uiContext: UIContext): FrameNode | null { 208 console.log(" uicontext is undefined : "+ (uiContext === undefined)); 209 if (this.rootnode != null) { 210 // Return the FrameNode. 211 return this.rootnode.getFrameNode(); 212 } 213 // Return null to detach the dynamic component from the bound node. 214 return null; 215 } 216 // Called when the layout size changes. 217 aboutToResize(size: Size) { 218 console.log("aboutToResize width : " + size.width + " height : " + size.height ); 219 } 220 221 // Called when the NodeContainer bound to the controller is about to appear. 222 aboutToAppear() { 223 console.log("aboutToAppear"); 224 } 225 226 // Called when the NodeContainer bound to the controller is about to disappear. 227 aboutToDisappear() { 228 console.log("aboutToDisappear"); 229 } 230 231 // This function is a custom function and can be used as an initialization function. 232 // Initialize BuilderNode through UIContext, and then initialize the content in @Builder through the build API in BuilderNode. 233 initWeb(url:ResourceStr, uiContext:UIContext, control:WebviewController) { 234 if(this.rootnode != null) 235 { 236 return; 237 } 238 // Create a node, during which the UIContext should be passed. 239 this.rootnode = new BuilderNode(uiContext); 240 // Create a dynamic Web component. 241 this.rootnode.build(wrap, { url:url, controller:control }); 242 } 243} 244// Create a Map to save the required NodeController. 245let NodeMap:Map<ResourceStr, myNodeController | undefined> = new Map(); 246// Create a Map to save the required WebViewController. 247let controllerMap:Map<ResourceStr, WebviewController | undefined> = new Map(); 248 249// UIContext is required for initialization and needs to be obtained from the ability. 250export const createNWeb = (url: ResourceStr, uiContext: UIContext) => { 251 // Create a NodeController instance. 252 let baseNode = new myNodeController(); 253 let controller = new webview.WebviewController() ; 254 // Initialize the custom Web component. 255 baseNode.initWeb(url, uiContext, controller); 256 controllerMap.set(url, controller) 257 NodeMap.set(url, baseNode); 258} 259// Customize the API for obtaining the NodeController. 260export const getNWeb = (url: ResourceStr) : myNodeController | undefined => { 261 return NodeMap.get(url); 262} 263``` 264 265```ts 266// index.ets 267import { webview } from '@kit.ArkWeb'; 268 269@Entry 270@Component 271struct Index1 { 272 webviewController: webview.WebviewController = new webview.WebviewController(); 273 274 build() { 275 Column() { 276 // The rendering process has been pre-started. 277 Button("Go to Web Page").onClick(()=>{ 278 this.getUIContext().getRouter().pushUrl({url: "pages/index2"}); 279 }) 280 .width('100%') 281 .height('100%') 282 } 283 } 284} 285``` 286 287```ts 288// index2.ets 289import { webview } from '@kit.ArkWeb'; 290 291@Entry 292@Component 293struct index2 { 294 webviewController: webview.WebviewController = new webview.WebviewController(); 295 296 build() { 297 Row() { 298 Column() { 299 Web({src: 'https://www.example.com', controller: this.webviewController}) 300 .width('100%') 301 .height('100%') 302 } 303 .width('100%') 304 } 305 .height('100%') 306 } 307} 308``` 309 310## Pre-rendering Web Pages 311 312Pre-rendering optimization is particularly beneficial for scenarios involving web page startup and redirection. For example, you can apply it in the situation where the user accesses the home page and is then redirected to a different page. For best possible effects, use this solution on pages that have a high cache hit ratio, meaning they are frequently revisited by users. 313 314To pre-render a web page, create an offline **Web** component in advance and activate it. The activation enables the rendering engine to initiate background rendering. 315 316> **NOTE** 317> 318> 1. For a web page to be pre-rendered successfully, identify the resources to be loaded beforehand. 319> 2. In this solution, the invisible **Web** component in the background is activated. Due to this activation, avoid pre-rendering pages that automatically play audio or video, as this could inadvertently lead to unintended media playback. Check and manage the behavior of the page on the application side. 320> 3. The pre-rendered web page is continuously rendered in the background. To prevent overheating and power consumption, you are advised to stop the rendering process immediately after the pre-rendering is complete. The following example shows how to use [onFirstMeaningfulPaint](../reference/apis-arkweb/arkts-basic-components-web-events.md#onfirstmeaningfulpaint12) to determine the time for stopping pre-rendering. This API can be used in HTTP and HTTPS online web pages. 321 322```ts 323// Carrier ability 324// EntryAbility.ets 325import {createNWeb} from "../pages/common"; 326import { UIAbility } from '@kit.AbilityKit'; 327import { window } from '@kit.ArkUI'; 328 329export default class EntryAbility extends UIAbility { 330 onWindowStageCreate(windowStage: window.WindowStage): void { 331 windowStage.loadContent('pages/Index', (err, data) => { 332 // Create a dynamic ArkWeb component, in which the UIContext should be passed. The component can be created at any time after loadContent() is called. 333 createNWeb("https://www.example.com", windowStage.getMainWindowSync().getUIContext()); 334 if (err.code) { 335 return; 336 } 337 }); 338 } 339} 340``` 341 342```ts 343// Create a NodeController instance. 344// common.ets 345import { UIContext } from '@kit.ArkUI'; 346import { webview } from '@kit.ArkWeb'; 347import { NodeController, BuilderNode, Size, FrameNode } from '@kit.ArkUI'; 348// @Builder contains the specific information of the dynamic component. 349// Data is an input parameter of encapsulation class. 350class Data{ 351 url: string = 'https://www.example.com'; 352 controller: WebviewController = new webview.WebviewController(); 353} 354// Use the Boolean variable shouldInactive to stop rendering after the web page is pre-rendered in the background. 355let shouldInactive: boolean = true; 356@Builder 357function WebBuilder(data:Data) { 358 Column() { 359 Web({ src: data.url, controller: data.controller }) 360 .onPageBegin(() => { 361 // Call onActive to enable rendering. 362 data.controller.onActive(); 363 }) 364 .onFirstMeaningfulPaint(() =>{ 365 if (!shouldInactive) { 366 return; 367 } 368 // Triggered when the pre-rendering is complete to stop rendering. 369 data.controller.onInactive(); 370 shouldInactive = false; 371 }) 372 .width("100%") 373 .height("100%") 374 } 375} 376let wrap = wrapBuilder<Data[]>(WebBuilder); 377// Used to control and report the behavior of the node in NodeContainer. This function must be used together with NodeContainer. 378export class myNodeController extends NodeController { 379 private rootnode: BuilderNode<Data[]> | null = null; 380 // This function must be overridden, which is used to construct the number of nodes, return the nodes and attach them to NodeContainer. 381 // Call it when the NodeContainer is created or call rebuild() to refresh. 382 makeNode(uiContext: UIContext): FrameNode | null { 383 console.info(" uicontext is undefined : "+ (uiContext === undefined)); 384 if (this.rootnode != null) { 385 // Return the FrameNode. 386 return this.rootnode.getFrameNode(); 387 } 388 // Return null to detach the dynamic component from the bound node. 389 return null; 390 } 391 // Called when the layout size changes. 392 aboutToResize(size: Size) { 393 console.info("aboutToResize width : " + size.width + " height : " + size.height ) 394 } 395 // Called when the NodeContainer bound to the controller is about to appear. 396 aboutToAppear() { 397 console.info("aboutToAppear") 398 // When the page is switched to the foreground, the rendering does not need to be stopped. 399 shouldInactive = false; 400 } 401 // Called when the NodeContainer bound to the controller is about to disappear. 402 aboutToDisappear() { 403 console.info("aboutToDisappear") 404 } 405 // This function is a custom function and can be used as an initialization function. 406 // Initialize BuilderNode through UIContext, and then initialize the content in @Builder through the build API in BuilderNode. 407 initWeb(url:string, uiContext:UIContext, control:WebviewController) { 408 if(this.rootnode != null) 409 { 410 return; 411 } 412 // Create a node, during which the UIContext should be passed. 413 this.rootnode = new BuilderNode(uiContext) 414 // Create a dynamic Web component. 415 this.rootnode.build(wrap, { url:url, controller:control }) 416 } 417} 418// Create a Map to save the required NodeController. 419let NodeMap:Map<string, myNodeController | undefined> = new Map(); 420// Create a Map to save the required WebViewController. 421let controllerMap:Map<string, WebviewController | undefined> = new Map(); 422// UIContext is required for initialization and needs to be obtained from the ability. 423export const createNWeb = (url: string, uiContext: UIContext) => { 424 // Create a NodeController instance. 425 let baseNode = new myNodeController(); 426 let controller = new webview.WebviewController() ; 427 // Initialize the custom Web component. 428 baseNode.initWeb(url, uiContext, controller); 429 controllerMap.set(url, controller) 430 NodeMap.set(url, baseNode); 431} 432// Customize the API for obtaining the NodeController. 433export const getNWeb = (url : string) : myNodeController | undefined => { 434 return NodeMap.get(url); 435} 436``` 437 438```ts 439// Use the pages of NodeController. 440// Index.ets 441import {createNWeb, getNWeb} from "./common"; 442 443@Entry 444@Component 445struct Index { 446 build() { 447 Row() { 448 Column() { 449 // NodeContainer is used to bind to the NodeController. A rebuild call triggers makeNode. 450 // The Page page is bound to the NodeController through the NodeContainer API to display the dynamic component. 451 NodeContainer(getNWeb("https://www.example.com")) 452 .height("90%") 453 .width("100%") 454 } 455 .width('100%') 456 } 457 .height('100%') 458 } 459} 460``` 461 462## Common Troubleshooting Procedure 463 4641. Check the network permission of the application. 465 466Make sure the network permission has been added to the **module.json5** file. For details, see [Declaring Permissions in the Configuration File](../security/AccessToken/declare-permissions.md). 467 468```ts 469"requestPermissions":[ 470 { 471 "name" : "ohos.permission.INTERNET" 472 } 473 ] 474``` 475 4762. Check the logic for binding [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md) to the node. 477 478Check whether the node has been added to the component tree. For diagnostics purposes, you are advised to add a **Text** component above the existing **Web** component, as shown in the following example. If the **Text** component is not displayed on the white screen, there is a potential issue with the binding between the NodeContainer and the node. 479 480```ts 481@Builder 482function WebBuilder(data:Data) { 483 Column() { 484 Text('test') 485 Web({ src: data.url, controller: data.controller }) 486 .width("100%") 487 .height("100%") 488 } 489} 490``` 491 4923. Check the visibility of the **Web** component. 493 494If the node has been added to the tree, examine the [WebPattern::OnVisibleAreaChange](../reference/apis-arkui/arkui-ts/ts-universal-component-visible-area-change-event.md#onvisibleareachange) log to check whether the **Web** component is visible. Invisible **Web** components may cause a white screen. 495