# webtabs_lab **Repository Path**: qing-lkyi/webtabs_lab ## Basic Information - **Project Name**: webtabs_lab - **Description**: OpenHarmony 高性能 ArkWeb 组件选项卡开发 - **Primary Language**: TypeScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2024-09-24 - **Last Updated**: 2024-11-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # WebTabs ## 简介 WebTabs 是基于 ArkWeb 和 ArkUI 开发的高性能 Web 选项卡。 它具有以下的特点: 1. 高性能:支持多种优化方式,支持滑动、点击标签等多种切换方式,多选项卡同时加载,第一次切换也能做到流畅无白屏 2. 高度可定制:可满足各种开发需求 - 支持内置 Swiper 和 Web 组件生命周期的自定义 - 内置简洁、可调节的 TabBar(标签栏)组件,支持使用外部自定义 TabBar 组件 - 提供访问 Web 和 Swiper 控件的接口,允许开发者自主控制 本三方库参与[2024年OH竞赛训练营](https://gitee.com/OpenHarmony-competition-camp/2024-Industry-Ecosystem-topic1)初赛,对应[挑战题二](https://gitee.com/OpenHarmony-competition-camp/2024-Industry-Ecosystem-topic1/blob/master/%E9%A2%98%E7%9B%AE%E4%BA%8C%EF%BC%9A%E4%BD%BF%E7%94%A8%E9%AB%98%E6%80%A7%E8%83%BDArkWeb%E7%BB%84%E4%BB%B6%E7%9A%84%E9%A1%B5%E9%9D%A2%E9%80%89%E9%A1%B9%E5%8D%A1.md)。 ![](./images/示例.gif) ## 下载安装 ```shell ohpm install @ncc/webtabs ``` OpenHarmony ohpm 环境配置等更多内容,请参考[如何安装 OpenHarmony ohpm 包](https://gitee.com/openharmony-tpc/docs/blob/master/OpenHarmony_har_usage.md) ## 接口列表 ### WebTabs 组件 #### 基本属性说明 | 属性名 | 必选 | 说明 | |-------------------------------|----|----------------------------------------------------------------------------| | itemList: WebTabItem[] | 是 | WebTabs 的资源列表,包含选项卡要加载的资源链接 webSrc 和 TabBar 标签的内容 label,**不建议**包含多于 4 个选项卡 | | controller: WebTabsController | 否 | WebTabs 控制器,可用于访问内部的 Web 组件和控制选项卡的切换 | | enable_tarBar: boolean | 否 | 是否使用默认标签,默认值 true,开启时会使用内置的 TabBarBuilder 创建选项卡标签 | | enable_preLoad: boolean | 否 | 是否启用预连接优化,默认值 true | | enable_preRender: boolean | 否 | 是否启用预渲染优化,默认值 true | | enable_lazy: boolean | 否 | 是否启用 LazyForEach 加载,默认值 false,仅 enable_preRender 为 true 时有效 | | enable_presetNode: boolean | 否 | 是否传入预先创建好的 NodeController,默认值 false,仅 enable_preRender 为 true 时有效 | | nodeObjects: WebNodeObject[] | 否 | presetNode 开启时必选。内容可由函数 createWebNode 生成 | preLoad 可以提供 60ms 左右的页面加载速度提升,建议开启;preRender 是无白屏切换的核心优化,建议开启。 enable_lazy 在 3-4 个选项卡情形下的提升效果并不明显,仅供开发者测试使用。开启时注意调整 Swiper 的 cachedCount。此外,由于 LazyForEach 的加载机制,组件会被卸载重建,在不使用预渲染组件的情况下会导致 Web 组件销毁、状态丢失,因此仅允许在开启预渲染时使用。 默认情形下,预渲染节点的创建发生在 WebTabs 组件的 aboutToAppear 中。如开发者有在别处初始化的需求,可使用 presetNode 相关属性直接传入已生成的 Node 列表。一般在性能调优中使用。 #### Swiper 相关属性说明 | 属性名 | 必选 | 说明 | |-------------------------------------------------------------------------------------------------|----|----------------------------------------| | enable_loop: boolean | 否 | 是否开启循环切换,默认值 false,开启时第一个选项卡与最后一个选项卡互通 | | curve: Curve | 否 | 设置动画曲线参数,默认值 Curve.Ease | | onChange: (index: number) => void | 否 | Swiper 事件。当前显示的子组件索引变化时触发该回调 | | onAnimationEnd: (index: number, extraInfo: SwiperAnimationEvent) => void | 否 | Swiper 事件。切换动画结束时触发该回调 | | onAnimationStart: (index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => void | 否 | Swiper 事件。切换动画开始时触发该回调 | | cachedCount: number | 否 | 设置预加载子组件个数,默认值 3 | 通过上面提供的三个事件属性,开发者可以自定义内部 Swiper 组件的相关回调。上述事件的具体说明参考[官方文档](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-container-swiper.md#%E4%BA%8B%E4%BB%B6)。 注意,在启用 LazyForEach 加载时,如果 cachedCount 无法覆盖到所有的页面,则**初次**切换到未被覆盖的页面上时会出现白屏闪烁。 #### WebBuilder 相关属性说明 WebTabs 为开发者提供了自定义 Web 组件生命周期的接口。 | 属性名 | 必选 | 说明 | |-----------------------------------|----|--------------------------------------------------------------------------------------------------------| | lifeCycleList: WebLifeCycle[] | 否 | LifeCycle 列表,长度要求与选项卡数量一致。每个 LifeCycle 类包含了对应 Web 组件的生命周期回调函数 | | cacheMode: CacheMode | 否 | 设置缓存模式。默认值 CacheMode.Default | | nestedScroll: NestedScrollOptions | 否 | 设置嵌套滚动选项。默认值 {scrollForward: NestedScrollMode.SELF_FIRST, scrollBackward: NestedScrollMode.SELF_FIRST} | | overScrollMode: OverScrollMode | 否 | 设置Web过滚动模式,默认值 OverScrollMode.NEVER。当过滚动模式开启时,当用户在Web界面上滑动到边缘时,Web会通过弹性动画弹回界面 | 嵌套滚动场景中,推荐设置 OverScrollMode 为 Never。 #### TabBar 相关属性说明 当 enable_tabBar 为 true 时,会使用内置的 tabBarBuilder 创建标签栏。该 Builder 会使用下面的参数。 当 enable_tabBar 为 false 时,下列参数无效,开分者可通过 controller 在外部自定义标签栏。 内置 TabBar Builder 固定在选项卡页面的上方,高度由文字大小决定。Web 组件将占满父组件余下的空间。 | 属性名 | 必选 | 说明 | |---------------------------------------------------------------------------------------------------|--------|----------------------------------------------------| | fontColor: ResourceColor
selectedFontColor: ResourceColor | 否 | 一般情况和选中情况下的标签字符颜色,一般默认值 Color.Gray,选中默认值 '#007DFF' | | fontSize: Length | 否 | 标签字符大小,默认值 20 | | fontWeight: string \| number \| FontWeight
selectedFontWeight: string \| number \| FontWeight | 否 | 一般情况和选中情况下的标签字符粗细,一般默认值 400,选中默认值 500 | | dividerVisible: Visibility | 否 | 选中标签下方的下划线是否可见,默认值 Visibility.Visible | | dividerColor: ResourceColor | 否 | 选中标签下方的下划线颜色,通常与选中标签字符颜色一致,默认值 '#007DFF' | | bgColor: ResourceColor | 否 | 标签栏背景颜色,默认值 Color.White | ### WebTabItem 类 WebTabItem 类或 WebTabItemType 接口用于存放选项卡的初始化资源链接和标签。 **属性说明**: | 属性名 | 必选 | 说明 | |---------------------|-----|---------------| | webSrc: ResourceStr | 是 | WebTabs 的资源链接 | | label: string | 是 | 标签的文本 | ### WebTabsController 类 用于访问 WebTabs 内部的相关控件,便于开发者对组件进行控制。 **属性说明**: | 属性名 | 必选 | 说明 | |---------------------------------------------|-----|---------------------------------------------------| | webControllers: webview.WebviewController[] | 不可选 | 每个选项卡对应的 WebviewController,在 WebTabs 加载时自动初始化 | | swiperController: SwiperController | 不可选 | 控制 WebTabs 内部的 Swiper 组件,创建时自动初始化 | | currentIndex: number | 不可选 | 当前选项卡序号,Swiper onChange 事件时自动刷新。仅用于辅助开发者开发 TabBar | **方法说明**: | 方法 | 说明 | |-----------------------------|--------------------------------| | getCurrentWebController(): webview.WebviewController | 返回当前页面(currentIndex)对应的 Web 控件 | ### WebLifeCycle 类 用于封装 Web 组件的生命周期回调函数,它们会在预渲染时挂载到对应 Web 组件上。目前仅支持下表中列出的属性名。 | 属性名 | 必选 | 说明 | |----------------------------------------------------------------------------|----|---------------------------------------------| | onControllerAttached: () => void; | 否 | 当 Controller 成功绑定到 Web 组件时触发该回调 | | onLoadIntercept: (event?: LoadInterceptEvent) => boolean | 否 | 当 Web 组件加载 url 之前触发该回调,用于判断是否阻止此次访问 | | onInterceptRequest: (event?: InterceptRequestEvent) => WebResourceResponse | 否 | 当 Web 组件加载 url 之前触发该回调,用于拦截 url 并返回响应数据 | | onPageBegin: (event?: PageEvent) | 否 | 网页开始加载时触发该回调,且只在主 frame 触发 | | onProgressChange: (event?: ProgressEvent) => void | 否 | 告知开发者当前页面加载的进度 | | onPageEnd: (event?: PageEvent) => void | 否 | 网页加载完成时触发该回调,且只在主 frame 触发 | | onPageVisible: (event?: PageEvent) => void | 否 | 当HTTP响应的主体开始加载,新页面即将可见时触发该回调,且只在主frame触发 | | onRenderExited: (event?: RenderExitedEvent) => void | 否 | 应用渲染进程异常退出时触发该回调, 可以在此回调中进行系统资源的释放、数据的保存等操作 | | onAttach: () => void | 否 | 组件挂载至组件树时触发此回调 | | onDetach: () => void | 否 | 组件从组件树卸载时触发此回调 | | onAppear: () => void | 否 | 组件挂载显示后触发此回调 | | onDisAppear: () => void | 否 | 组件卸载消失时触发此回调 | 关于 Web 组件的生命周期函数的接口细节和开发建议,参阅[官方文档](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/web/web-event-sequence.md)。 挂载卸载事件则参阅[这里](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-universal-events-show-hide.md)。 ### preRender 相关接口 下面的接口,只有当开发者开启 enable_presetNode,自己进行预渲染时会使用到。 #### WebBuilderParam 类 ```extendtypescript interface WebBuilderParamType { url: ResourceStr; // Web 链接 controller?: webview.WebviewController; // 控件 lifeCycle?: WebLifeCycle; // 生命周期回调函数 cacheMode?: CacheMode; // 缓存模式 overScrollMode?: OverScrollMode; // 过滚动选项 nestedScroll?: NestedScrollOptions; // 嵌套滚动选项 } export class WebBuilderParam implements WebBuilderParamType { url: ResourceStr = ''; controller: webview.WebviewController = new webview.WebviewController(); cacheMode: CacheMode = CacheMode.None; overScrollMode: OverScrollMode = OverScrollMode.NEVER; nestedScroll: NestedScrollOptions = { scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.PARENT_FIRST, } lifeCycle: WebLifeCycle = new WebLifeCycle({}); } ``` #### WebNodeObject 接口 ```extendtypescript export interface WebNodeObject { controller: webview.WebviewController, node: CustomNodeController } ``` #### createWebNode 函数 传入 UIContext 和相关参数,用于生成 NodeController。返回值包含了创建的预渲染 Web 组件的 NodeController 和 WebviewController。params 中没有传入的参数会依据默认值自动补齐。 ```extendtypescript export function createWebNode(uiContext: UIContext, params: WebBuilderParamType): WebNodeObject; ``` ## 使用示例 使用前,需要在模块的 oh-package.json5 中添加 `dependencies`: ```json5 { "dependencies": { "@ncc/webtabs": "1.0.0" } } ``` 添加后,即可在代码中引用相关类和组件。 **注意**:下面所有使用了在线页面的示例,需要 ohos.permission.INTERNET 权限。具体参阅[声明权限](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/security/AccessToken/declare-permissions.md)。 ### 基础示例 下面的例子创建了一个简单的 WebTabs。 ```extendtypescript import { WebTabs, WebTabItemType } from '@ncc/webtabs'; @Entry @Component struct Index { // 资源列表,使用 WebTabItemType 来初始化比较方便 @State itemList: WebTabItemType[] = [ { webSrc: 'https://developer.huawei.com/', label: 'huawei' }, { webSrc: $rawfile('index.html'), label: 'local' } // 支持本地文件 ]; build() { Column() { WebTabs({ itemList: this.itemList }) } } } ``` ### 通过 Controller 进行控制 通过 WebTabsController,可以实现对标签页的自定义控制。下面的例子演示了简单的用法: ```extendtypescript import { WebTabs, WebTabItemType, WebTabsController } from '@ncc/webtabs'; import { LengthMetrics } from '@ohos.arkui.node'; @Entry @Component struct Index { @State itemList: WebTabItemType[] = [ { webSrc: 'https://developer.huawei.com/', label: 'huawei' }, { webSrc: $rawfile('index.html'), label: 'local' } ]; @State controller: WebTabsController = new WebTabsController(); build() { Column({space: 20}) { Row() { WebTabs({ itemList: this.itemList, controller: this.controller, }) }.height('60%') // 通过 SwiperController 和 currentIndex 可以自定义页面的切换 Flex({ direction: FlexDirection.Row, space: {main: LengthMetrics.px(10)} }) { Button('ShowPrevious').onClick(() => { this.controller.swiperController.showPrevious(); }) Button('Goto1').onClick(() => { this.controller.swiperController.changeIndex(1, true); }) } // 通过 WebviewController 可以控制内部 Web 组件 Flex({ direction: FlexDirection.Row, space: {main: LengthMetrics.px(10)} }) { Button('Backward').onClick(() => { this.controller.getCurrentWebController().backward(); }) } } } } ``` ### 自定义 TabBar 通过 WebTabsController 的联动,可以进行灵活的 TabBar 自定义和布局控制。 下面展示了一个自定义 TabBar,并将其布置在 WebTabs 组件下方的例子: ```extendtypescript import { WebTabs, WebTabItemType, WebTabItem, WebTabsController } from '@ncc/webtabs'; import { LengthMetrics } from '@ohos.arkui.node'; @Entry @Component struct Index { @State itemList: WebTabItemType[] = [ { webSrc: 'https://developer.huawei.com/', label: 'huawei' }, { webSrc: $rawfile('index.html'), label: 'local' } ]; @State controller: WebTabsController = new WebTabsController(); build() { Column({space: 20}) { Row() { WebTabs({ itemList: this.itemList, controller: this.controller, enable_tabBar: false, }) }.height('70%') // 页面下方的 tabBar Row() { Flex({ direction: FlexDirection.Row }) { ForEach(this.itemList, (item: WebTabItem, index: number) => { Column() { Text(item.label) .fontSize(30) .fontColor(Color.White) .onClick(() => { this.controller.swiperController.changeIndex(index, true); }) .textAlign(TextAlign.Center) }.width('50%') .backgroundColor(this.controller.currentIndex == index ? Color.Blue : Color.Black) }) } } } } } ``` ### 自定义生命周期/事件 下面的例子展示了如何通过自定义 Web 组件和 Swiper 组件的生命周期/事件来实现当前 Url 的跟踪。在例子中,当 Web 组件完成页面加载或 Swiper 发生切换时,currentUrl 会更新: ```extendtypescript import { WebTabs, WebTabItemType, WebTabsController } from '@ncc/webtabs'; import { WebLifeCycle, CustomNodeController, WebBuilderParam } from '@ncc/webtabs'; @Entry @Component struct Index { @State tabController: WebTabsController = new WebTabsController(); @State itemList: WebTabItemType[] = [ { webSrc: 'https://developer.huawei.com/', label: 'huawei' }, { webSrc: $rawfile('index.html'), label: 'local' } ]; @State currentURL: string = 'Unloaded!'; private lifeCycleList: WebLifeCycle[] = []; aboutToAppear(): void { // 自定义 Web 组件生命周期,以获取实时 url this.itemList.forEach(_ => { this.lifeCycleList.push(new WebLifeCycle({ onPageEnd: () => { this.currentURL = this.tabController.getCurrentWebController().getUrl(); } })); }) } build() { Column({space: 20}) { Row() { WebTabs({ itemList: this.itemList, controller: this.tabController, lifeCycleList: this.lifeCycleList, // 自定义 Swiper 组件事件 onChange: () => { this.currentURL = this.tabController.getCurrentWebController().getUrl(); } }) }.height('70%') // 显示当前 Url Row() { Text('Current URL: ' + this.currentURL) .textAlign(TextAlign.Center) .maxLines(2).fontSize(20) .textOverflow({overflow: TextOverflow.Ellipsis}) } } .width('100%') .height('100%') } } ``` ### 自定义预渲染时机 通过 presetNode 选项,开发者可以在别处进行预渲染,再传入生成好的 NodeController。 下面的例子中,预渲染步骤在 EntryAbility.ets 的 onWindowStageCreate 提前完成: ```extendtypescript import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; import { window } from '@kit.ArkUI'; import { webview } from '@kit.ArkWeb'; import { WebTabs, WebNodeObject } from '@ncc/webtabs'; import { createWebNode } from '@ncc/webtabs'; // 预渲染的结果通过 export 导出 export let presetNodes: WebNodeObject[] = []; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { // 初始化 Web 组件内核 webview.WebviewController.initializeWebEngine(); } onWindowStageCreate(windowStage: window.WindowStage): void { windowStage.loadContent('pages/Index', (err) => { // 提前预渲染 let uiContext = windowStage.getMainWindowSync().getUIContext(); let urlList: ResourceStr[] = [ 'https://main.m.taobao.com/', 'https://sina.cn/', 'https://m.jd.com/', ] urlList.forEach((item, index) => { presetNodes.push(createWebNode(uiContext, {url: item})) }) }); } } ``` 通过 nodeObjects 属性传参: ```extendtypescript import { presetNodes } from '../entryability/EntryAbility'; import { WebTabs, WebTabItemType } from '@ncc/webtabs'; @Entry @Component struct Index { // 资源列表,使用 WebTabItemType 来初始化比较方便 @State itemList: WebTabItemType[] = [ { webSrc: 'https://developer.huawei.com/', label: 'huawei' }, { webSrc: $rawfile('index.html'), label: 'local' } // 支持本地文件 ]; build() { Column() { WebTabs({ itemList: this.itemList, enable_presetNode: true, nodeObjects: presetNodes }) } } } ``` ## 分析与实验 各种情形下的表现和分析实验,详见 docs 文件夹下的开发文档。 ## 遗留问题与开发计划 1. 暂未提供 Swiper Index 初始化的接口 2. 默认 TabBarBuilder 的标签栏按钮体积比看上去的小,对点按的精度有一定要求 3. 暂不支持 Web 组件的增删和默认 TabBar 的动态修改 4. 默认 TabBar 设计比较简单 ## 约束与限制 在下述版本验证通过:DevEco Studio: 5.0 Beta1(5.0.3.806),SDK: API12(5.0.0.25) ## 开源协议 本项目基于 [Apache-2.0](./LICENSE) ,请自由地享受和参与开源。