• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![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
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