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 optimize the pre-start rendering process and pre-rendering of 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-rendering of 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 266import router from '@ohos.router' 267@Entry 268@Component 269struct Index1 { 270 WebviewController: webview.WebviewController = new webview.WebviewController(); 271 272 build() { 273 Column() { 274 // The rendering process has been pre-started. 275 Button("Go to Web Page").onClick(()=>{ 276 router.pushUrl({url: "pages/index2"}) 277 }) 278 .width('100%') 279 .height('100%') 280 } 281 } 282} 283``` 284 285```ts 286import web_webview from '@ohos.web.webview' 287@Entry 288@Component 289struct index2 { 290 WebviewController: webview.WebviewController = new webview.WebviewController(); 291 292 build() { 293 Row() { 294 Column() { 295 Web({src: 'https://www.example.com', controller: this.webviewController}) 296 .width('100%') 297 .height('100%') 298 } 299 .width('100%') 300 } 301 .height('100%') 302 } 303} 304``` 305 306## Pre-rendering Web Pages 307 308Pre-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. 309 310To 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. 311 312> **NOTE** 313> 314> 1. For a web page to be pre-rendered successfully, identify the resources to be loaded beforehand. 315> 2. In this solution, the invisible **Web** component in the background is activated, which converts it to the **active** state. 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. 316> 3. In the background, the pre-rendered web page is continuously rendered. 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/ts-basic-components-web.md#onfirstmeaningfulpaint12) to determine the time for stopping pre-rendering. This API can be used in HTTP and HTTPS online web pages. 317 318```ts 319// Carrier ability 320// EntryAbility.ets 321import {createNWeb} from "../pages/common"; 322import { UIAbility } from '@kit.AbilityKit'; 323import { window } from '@kit.ArkUI'; 324 325export default class EntryAbility extends UIAbility { 326 onWindowStageCreate(windowStage: window.WindowStage): void { 327 windowStage.loadContent('pages/Index', (err, data) => { 328 // Create a dynamic ArkWeb component, in which the UIContext should be passed. The component can be created at any time after loadContent() is called. 329 createNWeb("https://www.example.com", windowStage.getMainWindowSync().getUIContext()); 330 if (err.code) { 331 return; 332 } 333 }); 334 } 335} 336``` 337 338```ts 339// Create a NodeController instance. 340// common.ets 341import { UIContext } from '@kit.ArkUI'; 342import { webview } from '@kit.ArkWeb'; 343import { NodeController, BuilderNode, Size, FrameNode } from '@kit.ArkUI'; 344// @Builder contains the specific information of the dynamic component. 345// Data is an input parameter of encapsulation class. 346class Data{ 347 url: string = 'https://www.example.com'; 348 controller: WebviewController = new webview.WebviewController(); 349} 350// Use the Boolean variable shouldInactive to stop rendering after the web page is pre-rendered in the background. 351let shouldInactive: boolean = true; 352@Builder 353function WebBuilder(data:Data) { 354 Column() { 355 Web({ src: data.url, controller: data.controller }) 356 .onPageBegin(() => { 357 // Call onActive to enable rendering. 358 data.controller.onActive(); 359 }) 360 .onFirstMeaningfulPaint(() =>{ 361 if (!shouldInactive) { 362 return; 363 } 364 // Triggered when the pre-rendering is complete to stop rendering. 365 data.controller.onInactive(); 366 shouldInactive = false; 367 }) 368 .width("100%") 369 .height("100%") 370 } 371} 372let wrap = wrapBuilder<Data[]>(WebBuilder); 373// Used to control and report the behavior of the node in NodeContainer. This function must be used together with NodeContainer. 374export class myNodeController extends NodeController { 375 private rootnode: BuilderNode<Data[]> | null = null; 376 // This function must be overridden, which is used to construct the number of nodes, return the nodes and attach them to NodeContainer. 377 // Call it when the NodeContainer is created or call rebuild() to refresh. 378 makeNode(uiContext: UIContext): FrameNode | null { 379 console.info(" uicontext is undifined : "+ (uiContext === undefined)); 380 if (this.rootnode != null) { 381 // Return the FrameNode. 382 return this.rootnode.getFrameNode(); 383 } 384 // Return null to detach the dynamic component from the bound node. 385 return null; 386 } 387 // Called when the layout size changes. 388 aboutToResize(size: Size) { 389 console.info("aboutToResize width : " + size.width + " height : " + size.height ) 390 } 391 // Called when the NodeContainer bound to the controller is about to appear. 392 aboutToAppear() { 393 console.info("aboutToAppear") 394 // When the page is switched to the foreground, the rendering does not need to be stopped. 395 shouldInactive = false; 396 } 397 // Called when the NodeContainer bound to the controller is about to disappear. 398 aboutToDisappear() { 399 console.info("aboutToDisappear") 400 } 401 // This function is a custom function and can be used as an initialization function. 402 // Initialize BuilderNode through UIContext, and then initialize the content in @Builder through the build API in BuilderNode. 403 initWeb(url:string, uiContext:UIContext, control:WebviewController) { 404 if(this.rootnode != null) 405 { 406 return; 407 } 408 // Create a node, during which the UIContext should be passed. 409 this.rootnode = new BuilderNode(uiContext) 410 // Create a dynamic Web component. 411 this.rootnode.build(wrap, { url:url, controller:control }) 412 } 413} 414// Create a Map to save the required NodeController. 415let NodeMap:Map<string, myNodeController | undefined> = new Map(); 416// Create a Map to save the required WebViewController. 417let controllerMap:Map<string, WebviewController | undefined> = new Map(); 418// UIContext is required for initialization and needs to be obtained from the ability. 419export const createNWeb = (url: string, uiContext: UIContext) => { 420 // Create a NodeController instance. 421 let baseNode = new myNodeController(); 422 let controller = new webview.WebviewController() ; 423 // Initialize the custom Web component. 424 baseNode.initWeb(url, uiContext, controller); 425 controllerMap.set(url, controller) 426 NodeMap.set(url, baseNode); 427} 428// Customize the API for obtaining the NodeController. 429export const getNWeb = (url : string) : myNodeController | undefined => { 430 return NodeMap.get(url); 431} 432``` 433 434```ts 435// Use the pages of NodeController. 436// Index.ets 437import {createNWeb, getNWeb} from "./common" 438 439@Entry 440@Component 441struct Index { 442 build() { 443 Row() { 444 Column() { 445 // NodeContainer is used to bind to the NodeController. A rebuild call triggers makeNode. 446 // The Page page is bound to the NodeController through the NodeContainer API to display the dynamic component. 447 NodeContainer(getNWeb("https://www.example.com")) 448 .height("90%") 449 .width("100%") 450 } 451 .width('100%') 452 } 453 .height('100%') 454 } 455} 456``` 457 458## Common Troubleshooting Procedure 459 4601. Check the network permission of the application. 461 462Make 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). 463 464```ts 465"requestPermissions":[ 466 { 467 "name" : "ohos.permission.INTERNET" 468 } 469 ] 470``` 471 4722. Check the logic for binding [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md) to the node. 473 474Check 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. 475 476```ts 477@Builder 478function WebBuilder(data:Data) { 479 Column() { 480 Text('test') 481 Web({ src: data.url, controller: data.controller }) 482 .width("100%") 483 .height("100%") 484 } 485} 486``` 487 4883. Check the visibility of the **Web** component. 489 490If 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. 491