1# 使用Web组件加载页面 2 3 4页面加载是Web组件的基本功能。根据页面加载数据来源可以分为三种常用场景,包括加载网络页面、加载本地页面、加载HTML格式的富文本数据。 5 6页面加载过程中,若涉及网络资源获取,请在module.json5中配置网络访问权限,添加方法请参考[在配置文件中声明权限](../security/AccessToken/declare-permissions.md)。 7 8 ``` 9 "requestPermissions":[ 10 { 11 "name" : "ohos.permission.INTERNET" 12 } 13 ] 14 ``` 15 16## 加载网络页面 17 18开发者可以在Web组件创建时,指定默认加载的网络页面 。在默认页面加载完成后,如果开发者需要变更此Web组件显示的网络页面,可以通过调用[loadUrl()](../reference/apis-arkweb/js-apis-webview.md#loadurl)接口加载指定的网页。[Web组件](../reference/apis-arkweb/ts-basic-components-web.md#web)的第一个参数变量src不能通过状态变量(例如:@State)动态更改地址,如需更改,请通过[loadUrl()](../reference/apis-arkweb/js-apis-webview.md#loadurl)重新加载。 19 20 21在下面的示例中,在Web组件加载完“www\.example.com”页面后,开发者可通过loadUrl接口将此Web组件显示页面变更为“www\.example1.com”。 22 23 24 25```ts 26// xxx.ets 27import { webview } from '@kit.ArkWeb'; 28import { BusinessError } from '@kit.BasicServicesKit'; 29 30@Entry 31@Component 32struct WebComponent { 33 controller: webview.WebviewController = new webview.WebviewController(); 34 35 build() { 36 Column() { 37 Button('loadUrl') 38 .onClick(() => { 39 try { 40 // 点击按钮时,通过loadUrl,跳转到www.example1.com 41 this.controller.loadUrl('www.example1.com'); 42 } catch (error) { 43 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 44 } 45 }) 46 // 组件创建时,加载www.example.com 47 Web({ src: 'www.example.com', controller: this.controller }) 48 } 49 } 50} 51``` 52 53 54## 加载本地页面 55 56在下面的示例中展示加载本地页面文件的方法: 57 58将本地页面文件放在应用的rawfile目录下,开发者可以在Web组件创建的时候指定默认加载的本地页面 ,并且加载完成后可通过调用[loadUrl()](../reference/apis-arkweb/js-apis-webview.md#loadurl)接口变更当前Web组件的页面。 59 60加载本地html文件时引用本地css样式文件可以通过下面方法实现。 61 62```html 63<link rel="stylesheet" href="resource://rawfile/xxx.css"> 64<link rel="stylesheet" href="file:///data/storage/el2/base/haps/entry/cache/xxx.css">// 加载沙箱路径下的本地css文件。 65``` 66 67- 将资源文件放置在应用的resources/rawfile目录下。 68 69 **图1** 资源文件路径 70 71  72 73 74- 应用侧代码。 75 76 ```ts 77 // xxx.ets 78 import { webview } from '@kit.ArkWeb'; 79 import { BusinessError } from '@kit.BasicServicesKit'; 80 81 @Entry 82 @Component 83 struct WebComponent { 84 controller: webview.WebviewController = new webview.WebviewController(); 85 86 build() { 87 Column() { 88 Button('loadUrl') 89 .onClick(() => { 90 try { 91 // 点击按钮时,通过loadUrl,跳转到local1.html 92 this.controller.loadUrl($rawfile("local1.html")); 93 } catch (error) { 94 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 95 } 96 }) 97 // 组件创建时,通过$rawfile加载本地文件local.html 98 Web({ src: $rawfile("local.html"), controller: this.controller }) 99 } 100 } 101 } 102 ``` 103 104 105- local.html页面代码。 106 107 ```html 108 <!-- local.html --> 109 <!DOCTYPE html> 110 <html> 111 <body> 112 <p>Hello World</p> 113 </body> 114 </html> 115 ``` 116 117- local1.html页面代码。 118 119 ```html 120 <!-- local1.html --> 121 <!DOCTYPE html> 122 <html> 123 <body> 124 <p>This is local1 page</p> 125 </body> 126 </html> 127 ``` 128 129加载沙箱路径下的本地页面文件。 130 1311. 通过构造的单例对象GlobalContext获取沙箱路径。 132 133 ```ts 134 // GlobalContext.ets 135 export class GlobalContext { 136 private constructor() {} 137 private static instance: GlobalContext; 138 private _objects = new Map<string, Object>(); 139 140 public static getContext(): GlobalContext { 141 if (!GlobalContext.instance) { 142 GlobalContext.instance = new GlobalContext(); 143 } 144 return GlobalContext.instance; 145 } 146 147 getObject(value: string): Object | undefined { 148 return this._objects.get(value); 149 } 150 151 setObject(key: string, objectClass: Object): void { 152 this._objects.set(key, objectClass); 153 } 154 } 155 ``` 156 157 ```ts 158 // xxx.ets 159 import { webview } from '@kit.ArkWeb'; 160 import { GlobalContext } from '../GlobalContext'; 161 162 let url = 'file://' + GlobalContext.getContext().getObject("filesDir") + '/index.html'; 163 164 @Entry 165 @Component 166 struct WebComponent { 167 controller: webview.WebviewController = new webview.WebviewController(); 168 169 build() { 170 Column() { 171 // 加载沙箱路径文件。 172 Web({ src: url, controller: this.controller }) 173 } 174 } 175 } 176 ``` 177 1782. 修改EntryAbility.ets。 179 180 以filesDir为例,获取沙箱路径。若想获取其他路径,请参考[应用文件路径](../application-models/application-context-stage.md#获取应用文件路径)。 181 182 ```ts 183 // xxx.ets 184 import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 185 import { webview } from '@kit.ArkWeb'; 186 import { GlobalContext } from '../GlobalContext'; 187 188 export default class EntryAbility extends UIAbility { 189 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { 190 // 通过在GlobalContext对象上绑定filesDir,可以实现UIAbility组件与UI之间的数据同步。 191 GlobalContext.getContext().setObject("filesDir", this.context.filesDir); 192 console.log("Sandbox path is " + GlobalContext.getContext().getObject("filesDir")); 193 } 194 } 195 ``` 196 197 加载的html文件。 198 199 ```html 200 <!-- index.html --> 201 <!DOCTYPE html> 202 <html> 203 <body> 204 <p>Hello World</p> 205 </body> 206 </html> 207 ``` 208 209 210## 加载HTML格式的文本数据 211 212Web组件可以通过[loadData()](../reference/apis-arkweb/js-apis-webview.md#loaddata)接口实现加载HTML格式的文本数据。当开发者不需要加载整个页面,只需要显示一些页面片段时,可通过此功能来快速加载页面,当加载大量html文件时,需设置第四个参数baseUrl为"data"。 213 214```ts 215// xxx.ets 216import { webview } from '@kit.ArkWeb'; 217import { BusinessError } from '@kit.BasicServicesKit'; 218 219@Entry 220@Component 221struct WebComponent { 222 controller: webview.WebviewController = new webview.WebviewController(); 223 224 build() { 225 Column() { 226 Button('loadData') 227 .onClick(() => { 228 try { 229 // 点击按钮时,通过loadData,加载HTML格式的文本数据 230 this.controller.loadData( 231 "<html><body bgcolor=\"white\">Source:<pre>source</pre></body></html>", 232 "text/html", 233 "UTF-8" 234 ); 235 } catch (error) { 236 console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); 237 } 238 }) 239 // 组件创建时,加载www.example.com 240 Web({ src: 'www.example.com', controller: this.controller }) 241 } 242 } 243} 244``` 245 246Web组件可以通过data url方式直接加载HTML字符串。 247 248```ts 249// xxx.ets 250import { webview } from '@kit.ArkWeb'; 251import { BusinessError } from '@kit.BasicServicesKit'; 252 253@Entry 254@Component 255struct WebComponent { 256 controller: webview.WebviewController = new webview.WebviewController(); 257 htmlStr: string = "data:text/html, <html><body bgcolor=\"white\">Source:<pre>source</pre></body></html>"; 258 259 build() { 260 Column() { 261 // 组件创建时,加载htmlStr 262 Web({ src: this.htmlStr, controller: this.controller }) 263 } 264 } 265} 266``` 267 268## 动态创建Web组件 269支持命令式创建Web组件,这种方式创建的组件不会立即挂载到组件树,即不会对用户呈现(组件状态为Hidden和InActive),开发者可以在后续使用中按需动态挂载。后台启动的Web实例不建议超过200个。 270 271```ts 272// 载体Ability 273// EntryAbility.ets 274import { createNWeb } from "../pages/common" 275onWindowStageCreate(windowStage: window.WindowStage): void { 276 windowStage.loadContent('pages/Index', (err, data) => { 277 // 创建Web动态组件(需传入UIContext),loadContent之后的任意时机均可创建 278 createNWeb("https://www.example.com", windowStage.getMainWindowSync().getUIContext()); 279 if (err.code) { 280 return; 281 } 282 }); 283} 284``` 285```ts 286// 创建NodeController 287// common.ets 288import { UIContext, NodeController, BuilderNode, Size, FrameNode } from '@kit.ArkUI'; 289import { webview } from '@kit.ArkWeb'; 290 291// @Builder中为动态组件的具体组件内容 292// Data为入参封装类 293class Data{ 294 url: string = "https://www.example.com"; 295 controller: WebviewController = new webview.WebviewController(); 296} 297 298@Builder 299function WebBuilder(data:Data) { 300 Column() { 301 Web({ src: data.url, controller: data.controller }) 302 .width("100%") 303 .height("100%") 304 } 305} 306 307let wrap = wrapBuilder<Data[]>(WebBuilder); 308 309// 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用 310export class myNodeController extends NodeController { 311 private rootnode: BuilderNode<Data[]> | null = null; 312 // 必须要重写的方法,用于构建节点数、返回节点挂载在对应NodeContainer中 313 // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新 314 makeNode(uiContext: UIContext): FrameNode | null { 315 console.log(" uicontext is undefined : "+ (uiContext === undefined)); 316 if (this.rootnode != null) { 317 // 返回FrameNode节点 318 return this.rootnode.getFrameNode(); 319 } 320 // 返回null控制动态组件脱离绑定节点 321 return null; 322 } 323 // 当布局大小发生变化时进行回调 324 aboutToResize(size: Size) { 325 console.log("aboutToResize width : " + size.width + " height : " + size.height ); 326 } 327 328 // 当controller对应的NodeContainer在Appear的时候进行回调 329 aboutToAppear() { 330 console.log("aboutToAppear"); 331 } 332 333 // 当controller对应的NodeContainer在Disappear的时候进行回调 334 aboutToDisappear() { 335 console.log("aboutToDisappear"); 336 } 337 338 // 此函数为自定义函数,可作为初始化函数使用 339 // 通过UIContext初始化BuilderNode,再通过BuilderNode中的build接口初始化@Builder中的内容 340 initWeb(url:string, uiContext:UIContext, control:WebviewController) { 341 if(this.rootnode != null) 342 { 343 return; 344 } 345 // 创建节点,需要uiContext 346 this.rootnode = new BuilderNode(uiContext); 347 // 创建动态Web组件 348 this.rootnode.build(wrap, { url:url, controller:control }); 349 } 350} 351// 创建Map保存所需要的NodeController 352let NodeMap:Map<string, myNodeController | undefined> = new Map(); 353// 创建Map保存所需要的WebViewController 354let controllerMap:Map<string, WebviewController | undefined> = new Map(); 355 356// 初始化需要UIContext 需在Ability获取 357export const createNWeb = (url: string, uiContext: UIContext) => { 358 // 创建NodeController 359 let baseNode = new myNodeController(); 360 let controller = new webview.WebviewController() ; 361 // 初始化自定义Web组件 362 baseNode.initWeb(url, uiContext, controller); 363 controllerMap.set(url, controller) 364 NodeMap.set(url, baseNode); 365} 366// 自定义获取NodeController接口 367export const getNWeb = (url : string) : myNodeController | undefined => { 368 return NodeMap.get(url); 369} 370``` 371```ts 372// 使用NodeController的Page页 373// Index.ets 374import { getNWeb } from "./common" 375@Entry 376@Component 377struct Index { 378 build() { 379 Row() { 380 Column() { 381 // NodeContainer用于与NodeController节点绑定,rebuild会触发makeNode 382 // Page页通过NodeContainer接口绑定NodeController,实现动态组件页面显示 383 NodeContainer(getNWeb("https://www.example.com")) 384 .height("90%") 385 .width("100%") 386 } 387 .width('100%') 388 } 389 .height('100%') 390 } 391} 392 393``` 394**常见白屏问题排查:** 3951. 排查应用上网权限配置。 396 397 检查是否已在module.json5中添加网络权限,添加方法请参考在[配置文件中声明权限](../security/AccessToken/declare-permissions.md)。 398 ```ts 399 "requestPermissions":[ 400 { 401 "name" : "ohos.permission.INTERNET" 402 } 403 ] 404 ``` 4052. 排查NodeContainer与节点绑定的逻辑。 406 407 检查节点是否已上组件树,建议在已有的Web组件上方加上Text(请参考以下例子),如果白屏的时候没有出现Text,建议检查NodeContainer与节点的绑定 408 ```ts 409 @Builder 410 function WebBuilder(data:Data) { 411 Column() { 412 Text('test') 413 Web({ src: data.url, controller: data.controller }) 414 .width("100%") 415 .height("100%") 416 } 417 } 418 ``` 4193. 排查Web可见性状态。 420 421 如果整个节点已上树,可通过日志[WebPattern::OnVisibleAreaChange](../reference/apis-arkui/arkui-ts/ts-universal-component-visible-area-change-event.md#onvisibleareachange) 查看Web组件可见性状态是否正确,不可见的Web组件可能会造成白屏 422## 相关实例 423 424针对Web组件开发,有以下相关实例可供参考: 425 426- [浏览器(ArkTS)(Full SDK)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-5.0.1-Release/code/BasicFeature/Web/Browser) 427