• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Web组件嵌套滚动
2<!--Kit: ArkWeb-->
3<!--Subsystem: Web-->
4<!--Owner: @zourongchun-->
5<!--Designer: @zhufenghao-->
6<!--Tester: @ghiker-->
7<!--Adviser: @HelloCrease-->
8
9Web组件嵌套滚动的典型应用场景为,在页面中,多个独立区域需进行滚动,当用户滚动Web区域内容时,可联动其他滚动区域,实现上下左右全方位滑动页面的嵌套滚动体验。内嵌于可滚动容器([Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md)、[List](../reference/apis-arkui/arkui-ts/ts-container-list.md)、[Scroll](../reference/apis-arkui/arkui-ts/ts-container-scroll.md)、[Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md)、[Tabs](../reference/apis-arkui/arkui-ts/ts-container-tabs.md)、[WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md)、[Refresh](../reference/apis-arkui/arkui-ts/ts-container-refresh.md)、[bindSheet](../reference/apis-arkui/arkui-ts/ts-universal-attributes-sheet-transition.md#bindsheet))中的Web组件,接收到滑动手势事件后,需要设置ArkUI的[NestedScrollMode](../reference/apis-arkui/arkui-ts/ts-appendix-enums.md#nestedscrollmode10)枚举属性,实现Web组件与ArkUI可滚动容器的嵌套滚动。
10
11Web组件嵌套滚动可通过[方案1:使用nestedScroll属性实现嵌套滚动](#使用nestedscroll属性实现嵌套滚动)或[方案2:滚动偏移量由滚动父组件统一派发](#滚动偏移量由滚动父组件统一派发)两个方案实现,方案的选择应取决于应用嵌套滚动的具体业务场景。如果只是简单的Web组件与其他父组件联动滚动建议通过方案1实现;如果应用需要自定义控制Web组件和其他滚动组件滚动,以及一些复杂场景建议使用方案2。
12
13> **说明:**
14>
15> 如果Web组件用到了全量展开的场景(layoutMode为`WebLayoutMode.FIT_CONTENT`),需要显式指明渲染模式(`RenderMode.SYNC_RENDER`),详见[layoutMode](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#layoutmode11)。
16
17## 使用nestedScroll属性实现嵌套滚动
18
19使用Web组件[nestedScroll](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#nestedscroll11)属性来设置上下左右四个方向,或者设置向前、向后两个方向的嵌套滚动模式,实现与父组件的滚动联动,同时也允许在过程中动态改变嵌套滚动的模式。
20
21**完整代码**
22```ts
23// xxx.ets
24import { webview } from '@kit.ArkWeb';
25
26@Entry
27@ComponentV2
28struct NestedScroll {
29  private scrollerForScroll: Scroller = new Scroller()
30  private listScroller: Scroller = new Scroller()
31  controller: webview.WebviewController = new webview.WebviewController();
32  @Local arr: Array<number> = []
33
34  aboutToAppear(): void {
35    for (let i = 0; i < 10; i++) {
36      this.arr.push(i)
37    }
38  }
39
40  build() {
41    Scroll(this.scrollerForScroll) {
42      Column() {
43        Web({ src: $rawfile("index.html"), controller: this.controller })
44          .nestedScroll({
45            scrollUp: NestedScrollMode.PARENT_FIRST,//向上滚动父组件优先
46            scrollDown: NestedScrollMode.SELF_FIRST,//向下滚动子组件优先
47          }).height("100%")
48        Repeat<number>(this.arr)
49          .each((item: RepeatItem<number>) => {
50            Text("Scroll Area")
51              .width("100%")
52              .height("40%")
53              .backgroundColor(0X330000FF)
54              .fontSize(16)
55              .textAlign(TextAlign.Center)
56          })
57      }
58    }
59  }
60}
61```
62加载的html文件。
63
64```html
65<!-- index.html -->
66<!DOCTYPE html>
67<html>
68<head>
69    <meta name="viewport" id="viewport" content="width=device-width, initial-scale=1.0">
70    <style>
71        .blue {
72          background-color: lightblue;
73        }
74        .green {
75          background-color: lightgreen;
76        }
77        .blue, .green {
78         font-size:16px;
79         height:200px;
80         text-align: center;       /* 水平居中 */
81         line-height: 200px;       /* 垂直居中(值等于容器高度) */
82        }
83    </style>
84</head>
85<body>
86<div class="blue" >webArea</div>
87<div class="green">webArea</div>
88<div class="blue">webArea</div>
89<div class="green">webArea</div>
90<div class="blue">webArea</div>
91<div class="green">webArea</div>
92<div class="blue">webArea</div>
93</body>
94</html>
95```
96![web-nested-scrolling](figures/web-nested-scrolling2.gif)
97
98## 滚动偏移量由滚动父组件统一派发
99
100**实现思路**
101
1021. 手指向上划动:
103
104    (1) 如果Web页面没有滚动到底部,Scroll组件将滚动偏移量派发给Web,Scroll组件自身不滚动。
105
106    (2) 如果Web页面滚动至底部,而Scroll组件尚未滚动至底部,则仅Scroll组件自身滚动,不向Web组件和List组件传递滚动位移。
107
108    (3) 如果Scroll组件滚动到底部,则滚动偏移量派发给List组件,Scroll组件自身不滚动。
1092. 手指向下划动:
110
111    (1) 如果List组件没有滚动到顶部,则Scroll组件将滚动偏移量派发给List组件,Scroll组件自身不滚动。
112
113    (2) 当List组件滚动至顶部,而Scroll组件未到达顶部时,Scroll组件将自行滚动,滚动偏移量不会派发给List组件和Web组件。
114
115    (3) 如果Scroll组件滚动到顶部,则滚动偏移量派发给Web,Scroll组件自身不滚动。
116
117**关键实现**
118
1191. 如何禁用Web组件滚动手势。
120
121    (1) 首先调用Web组件滚动控制器方法,设置Web禁用触摸([setScrollable](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#setscrollable12))的滚动。
122    ```ts
123    this.webController.setScrollable(false, webview.ScrollType.EVENT);
124    ```
125    (2) 再使用[onGestureRecognizerJudgeBegin](../reference/apis-arkui/arkui-ts/ts-gesture-blocking-enhancement.md#ongesturerecognizerjudgebegin13)方法,禁止Web组件自带的滑动手势触发。
126    ```ts
127    .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer, otherArray<GestureRecognizer>) => {
128      if (current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) {
129        return GestureJudgeResult.REJECT;
130      }
131      return GestureJudgeResult.CONTINUE;
132    })
133    ```
1342. 如何禁止[List](../reference/apis-arkui/arkui-ts/ts-container-list.md)组件的手势。
135    ```ts
136	  .enableScrollInteraction(false)
137    ```
1383. 如何检测List组件、Scroll组件是否滚动到边界。
139
140	(1) 滚动到上边界:scroller.currentOffset().yOffset <= 0;
141
142	(2) 滚动到下边界:scroller.isAtEnd() == true;
143
1444. 如何检测Web组件是否滚动到边界。
145
146	(1) 获取Web组件自身高度、内容高度和当前滚动偏移量来判定。
147
148	(2) 判断Web组件是否滚动到顶部:webController.getPageOffset().y == 0;
149
150	(3) 判断Web组件是否滚动到底部:webController.getPageOffset().y + this.webHeight >= webController.getPageHeight();
151
152	(4) 获取Web组件自身高度:webController.[getPageHeight()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#getpageheight);
153
154	(5) 获取Web组件窗口高度:webController?.[runJavaScriptExt](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#runjavascriptext10)('window. innerHeight');
155
156	(6) 获取Web组件的滚动偏移量:webController.[getPageOffset()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#getpageoffset20);
1575. 如何让Scroll组件不滚动。
158
159	Scroll组件绑定[onScrollFrameBegin](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#onscrollframebegin9)事件,将剩余滚动偏移量返回0,scroll组件就不滚动,也不会停止惯性滚动动画。
1606. 滚动偏移量如何派发给List。
161    ```ts
162	  this.listScroller.scrollBy(0, offset)
163    ```
1647. 滚动偏移量如何派发给Web。
165    ```ts
166	  this.webController.scrollBy(0, offset)
167    ```
1688. 设置Web组件[bypassVsyncCondition](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#bypassvsynccondition20)为WebBypassVsyncCondition.SCROLLBY_FROM_ZERO_OFFSET,加快Web组件首帧滚动绘制。
169    ```ts
170	  .bypassVsyncCondition(WebBypassVsyncCondition.SCROLLBY_FROM_ZERO_OFFSET)
171    ```
172
173**完整代码**
174```ts
175// xxx.ets
176import { webview } from '@kit.ArkWeb';
177
178@Entry
179@ComponentV2
180struct Index {
181  private scroller:Scroller = new Scroller()
182  private listScroller:Scroller = new Scroller()
183  private webController: webview.WebviewController = new webview.WebviewController()
184  private isWebAtEnd:boolean = false
185  private webHeight:number = 0
186  @Local arr: Array<number> = []
187
188  aboutToAppear(): void {
189    for (let i = 0; i < 10; i++) {
190      this.arr.push(i)
191    }
192  }
193
194  getWebHeight() {
195    try {
196      this.webController?.runJavaScriptExt('window.innerHeight',
197        (error, result) => {
198          if (error || !result) {
199            return;
200          }
201          if (result.getType() === webview.JsMessageType.NUMBER) {
202            this.webHeight = result.getNumber()
203          }
204        })
205    } catch (error) {
206    }
207  }
208
209  checkScrollBottom() {
210  	this.isWebAtEnd = false;
211  	if (this.webController.getPageOffset().y + this.webHeight >= this.webController.getPageHeight()) {
212  	  this.isWebAtEnd = true;
213  	}
214  }
215
216  build() {
217    Scroll(this.scroller) {
218      Column() {
219        Web({
220          src: $rawfile("index.html"),
221          controller: this.webController,
222        }).height("100%")
223          .bypassVsyncCondition(WebBypassVsyncCondition.SCROLLBY_FROM_ZERO_OFFSET)
224          .onPageEnd(() => {
225            this.webController.setScrollable(false, webview.ScrollType.EVENT);
226            this.getWebHeight();
227          })
228            // 在识别器即将要成功时,根据当前组件状态,设置识别器使能状态
229          .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer, others: Array<GestureRecognizer>) => {
230            if (current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) {
231              return GestureJudgeResult.REJECT;
232            }
233            return GestureJudgeResult.CONTINUE;
234          })
235        List({ scroller: this.listScroller }) {
236          Repeat<number>(this.arr)
237            .each((item: RepeatItem<number>) => {
238              ListItem() {
239                Text("Scroll Area")
240                  .width("100%")
241                  .height("40%")
242                  .backgroundColor(0X330000FF)
243                  .fontSize(16)
244                  .textAlign(TextAlign.Center)
245              }
246            })
247        }.height("100%")
248        .maintainVisibleContentPosition(true)
249        .enableScrollInteraction(false)
250      }
251    }
252    .onScrollFrameBegin((offset: number, state: ScrollState)=>{
253      this.checkScrollBottom();
254      if (offset > 0) {
255        if (!this.isWebAtEnd) {
256          this.webController.scrollBy(0, offset)
257          return {offsetRemain:0}
258        } else if (this.scroller.isAtEnd()) {
259          this.listScroller.scrollBy(0, offset)
260          return {offsetRemain:0}
261        }
262      } else if (offset < 0) {
263        if (this.listScroller.currentOffset().yOffset > 0) {
264          this.listScroller.scrollBy(0, offset)
265          return {offsetRemain:0}
266        } else if (this.scroller.currentOffset().yOffset <= 0) {
267          this.webController.scrollBy(0, offset)
268          return {offsetRemain:0}
269        }
270      }
271      return {offsetRemain:offset}
272    })
273  }
274}
275```
276加载的html文件。
277
278```html
279<!-- index.html -->
280<!DOCTYPE html>
281<html>
282<head>
283    <meta name="viewport" id="viewport" content="width=device-width, initial-scale=1.0">
284    <style>
285        .blue {
286          background-color: lightblue;
287        }
288        .green {
289          background-color: lightgreen;
290        }
291        .blue, .green {
292         font-size:16px;
293         height:200px;
294         text-align: center;       /* 水平居中 */
295         line-height: 200px;       /* 垂直居中(值等于容器高度) */
296        }
297    </style>
298</head>
299<body>
300<div class="blue" >webArea</div>
301<div class="green">webArea</div>
302<div class="blue">webArea</div>
303<div class="green">webArea</div>
304<div class="blue">webArea</div>
305<div class="green">webArea</div>
306<div class="blue">webArea</div>
307</body>
308</html>
309```
310![web-nested-scrolling](figures/web-gesture-scrolling.gif)
311<!--RP1--><!--RP1End-->