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![web-offline-mode](figures/web-offline-mode.png)
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