• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Implementing Nested Scrolling
2
3There may be times when you want to implement nested scrolling for the **Web** component. A typical use case is a page that contains multiple scrollable areas including the **Web** component, whose scrolling is intrinsically linked with the scroll positions in other areas. To implement nested scrolling between **Web** components and ArkUI scrollable containers ([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) and [bindSheet](../reference/apis-arkui/arkui-ts/ts-universal-attributes-sheet-transition.md#bindsheet)), you should set the ArkUI [NestedScrollMode](../reference/apis-arkui/arkui-ts/ts-appendix-enums.md#nestedscrollmode10) attribute for the **Web** components after receiving the scrolling gesture events.
4
5The following provides two solutions for implementing nested scrolling of **Web** components: Solution 1: [Using the nestedScroll Attribute](#using-the-nestedscroll-attribute); Solution 2: [Distributing Scrolling Offsets Through the Parent Scroll Component](#distributing-scrolling-offsets-through-the-parent-scroll-component). You can select a solution based on the specific service scenario. To associate **Web** components with other parent components, you are advised to use solution 1. To customize the scrolling of **Web** components and other scrolling components, especially in some complex scenarios, you are advised to use solution 2.
6
7> **NOTE**
8>
9> If a **Web** component uses full expansion (**layoutMode** is set to **WebLayoutMode.FIT_CONTENT**), the rendering mode (**RenderMode.SYNC_RENDER**) must be explicitly specified. For details, see [layoutMode](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#layoutmode11).
10
11## Using the nestedScroll Attribute
12
13Call the [nestedScroll](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#nestedscroll11) attribute of the **Web** component to set the nested scrolling mode in the up, down, left, and right directions or in the forward and backward directions to implement the scrolling association with the parent component. This attribute also allows the nested scrolling mode to be dynamically changed during the process.
14
15**Complete Code**
16```ts
17// xxx.ets
18import { webview } from '@kit.ArkWeb';
19
20@Entry
21@ComponentV2
22struct NestedScroll {
23  private scrollerForScroll: Scroller = new Scroller()
24  private listScroller: Scroller = new Scroller()
25  controller: webview.WebviewController = new webview.WebviewController();
26  @Local arr: Array<number> = []
27
28  aboutToAppear(): void {
29    for (let i = 0; i < 10; i++) {
30      this.arr.push(i)
31    }
32  }
33
34  build() {
35    Scroll(this.scrollerForScroll) {
36      Column() {
37        Web({ src: $rawfile("index.html"), controller: this.controller })
38          .nestedScroll({
39            scrollUp: NestedScrollMode.PARENT_FIRST,// Scroll up the parent component first.
40            scrollDown: NestedScrollMode.SELF_FIRST,// Scroll down the child component first.
41          }).height("100%")
42        Repeat<number>(this.arr)
43          .each((item: RepeatItem<number>) => {
44            Text("Scroll Area")
45              .width("100%")
46              .height("40%")
47              .backgroundColor(0X330000FF)
48              .fontSize(16)
49              .textAlign(TextAlign.Center)
50          })
51      }
52    }
53  }
54}
55```
56HTML file to be loaded:
57
58```html
59<!-- index.html -->
60<!DOCTYPE html>
61<html>
62<head>
63    <meta name="viewport" id="viewport" content="width=device-width, initial-scale=1.0">
64    <style>
65        .blue {
66          background-color: lightblue;
67        }
68        .green {
69          background-color: lightgreen;
70        }
71        .blue, .green {
72         font-size:16px;
73         height:200px;
74         text-align: center;       /* Horizontally centered */
75         line-height: 200px;       /* Vertically centered (the height matches the container height) */
76        }
77    </style>
78</head>
79<body>
80<div class="blue" >webArea</div>
81<div class="green">webArea</div>
82<div class="blue">webArea</div>
83<div class="green">webArea</div>
84<div class="blue">webArea</div>
85<div class="green">webArea</div>
86<div class="blue">webArea</div>
87</body>
88</html>
89```
90![web-nested-scrolling](figures/web-nested-scrolling2.gif)
91
92## Distributing Scrolling Offsets Through the Parent Scroll Component
93
94**How to Implement**
95
961. Scrolling up:
97
98    (1) If the web page does not scroll to the bottom, the **Scroll** component sends the scrolling offset to the **Web** component, and the **Scroll** component does not scroll.
99
100    (2) If the web page scrolls to the bottom but the **Scroll** component does not scroll to the bottom, only the **Scroll** component scrolls and it does not send the scrolling offset to the **Web** and **List** components.
101
102    (3) If the **Scroll** component scrolls to the bottom, the scrolling offset is sent to the **List** component, and the **Scroll** component does not scroll.
1032. Scrolling down:
104
105    (1) If the **List** component does not scroll to the top, the **Scroll** component sends the scrolling offset to the **List** component, and the **Scroll** component does not scroll.
106
107    (2) When the **List** component scrolls to the top but the **Scroll** component does not, the **Scroll** component automatically scrolls. The scrolling offset is not sent to the **List** component and **Web** component.
108
109    (3) If the **Scroll** component scrolls to the top, the scrolling offset is sent to the **Web** component, and the **Scroll** component does not scroll.
110
111**Implementation**
112
1131. Disable the scrolling gestures of the **Web** component.
114
115    (1) Call [setScrollable](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#setscrollable12) to disable touch scrolling.
116
117    ```ts
118    this.webController.setScrollable(false, webview.ScrollType.EVENT);
119    ```
120    (2) Call [onGestureRecognizerJudgeBegin](../reference/apis-arkui/arkui-ts/ts-gesture-blocking-enhancement.md#ongesturerecognizerjudgebegin13) to disable the scrolling gesture of the **Web** component.
121
122		```ts
123		.onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer, others: Array<GestureRecognizer>) => {
124		if (current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) {
125			return GestureJudgeResult.REJECT;
126		}
127		return GestureJudgeResult.CONTINUE;
128		})
129		```
1302. Disable the gestures of the [List](../reference/apis-arkui/arkui-ts/ts-container-list.md) component.
131    ```ts
132	  .enableScrollInteraction(false)
133    ```
1343. Check whether the **List** and **Scroll** components scroll to the boundary.
135
136	(1) Scroll to the top boundary: **scroller.currentOffset().yOffset <= 0**;
137
138	(2) Scroll to the bottom boundary: **scroller.isAtEnd() == true**;
139
1404. Check whether the **Web** component scrolls to the boundary.
141
142	(1) Obtain the height, content height, and current scrolling offset of the **Web** component.
143
144	(2) Scroll to the top boundary: **webController.getScrollOffset() == 0**;
145
146	(3) Scroll to the bottom boundary: **webController.getScrollOffset().y + this.webHeight >= webController.getPageHeight()**;
147
148	(4) Height of the **Web** component: **webController.[getPageHeight()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#getpageheight)**;
149
150	(5) Height of the **Web** component window: **webController?.[runJavaScriptExt](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#runjavascriptext10)('window. innerHeight')**;
151
152	(6) Scrolling offset of the **Web** component: **webController.[getScrollOffset()](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#getscrolloffset13)**;
1535. Disable the scrolling feature of the **Scroll** component.
154
155	Bind the **Scroll** component to the [onScrollFrameBegin](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#onscrollframebegin9) event and set the remaining scrolling offset to **0**. Then the **Scroll** component does not scroll, and the inertial scrolling animation does not stop.
1566. Distribute the scrolling offset to the **List** component.
157    ```ts
158	  this.listScroller.scrollBy(0, offset)
159    ```
1607. Distribute the scrolling offset to the **Web** component.
161    ```ts
162	  this.webController.scrollBy(0, offset)
163    ```
164**Complete Code**
165```ts
166// xxx.ets
167import { webview } from '@kit.ArkWeb';
168
169@Entry
170@ComponentV2
171struct Index {
172  private scroller:Scroller = new Scroller()
173  private listScroller:Scroller = new Scroller()
174  private webController: webview.WebviewController = new webview.WebviewController()
175  private isWebAtEnd:boolean = false
176  private webHeight:number = 0
177  private scrollTop:number = 0
178  @Local arr: Array<number> = []
179
180  aboutToAppear(): void {
181    for (let i = 0; i < 10; i++) {
182      this.arr.push(i)
183    }
184  }
185
186  getWebHeight() {
187    try {
188      this.webController?.runJavaScriptExt('window.innerHeight',
189        (error, result) => {
190          if (error || !result) {
191            return;
192          }
193          if (result.getType() === webview.JsMessageType.NUMBER) {
194            this.webHeight = result.getNumber()
195          }
196        })
197    } catch (error) {
198    }
199  }
200
201  getWebScrollTop() {
202  	this.isWebAtEnd = false;
203  	if (this.webController.getScrollOffset().y + this.webHeight >= this.webController.getPageHeight()) {
204  	  this.isWebAtEnd = true;
205  	}
206  }
207
208  build() {
209    Scroll(this.scroller) {
210      Column() {
211        Web({
212          src: $rawfile("index.html"),
213          controller: this.webController,
214        }).height("100%")
215          .onPageEnd(() => {
216            this.webController.setScrollable(false, webview.ScrollType.EVENT);
217            this.getWebHeight();
218          })
219            // When the recognizer is about to succeed, set whether to enable the recognizer based on the current component status.
220          .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer, others: Array<GestureRecognizer>) => {
221            if (current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) {
222              return GestureJudgeResult.REJECT;
223            }
224            return GestureJudgeResult.CONTINUE;
225          })
226        List({ scroller: this.listScroller }) {
227          Repeat<number>(this.arr)
228            .each((item: RepeatItem<number>) => {
229              ListItem() {
230                Text("Scroll Area")
231                  .width("100%")
232                  .height("40%")
233                  .backgroundColor(0X330000FF)
234                  .fontSize(16)
235                  .textAlign(TextAlign.Center)
236              }
237            })
238        }.height("100%")
239        .maintainVisibleContentPosition(true)
240        .enableScrollInteraction(false)
241      }
242    }
243    .onScrollFrameBegin((offset: number, state: ScrollState)=>{
244      this.getWebScrollTop();
245      if (offset > 0) {
246        if (!this.isWebAtEnd) {
247          this.webController.scrollBy(0, offset)
248          return {offsetRemain:0}
249        } else if (this.scroller.isAtEnd()) {
250          this.listScroller.scrollBy(0, offset)
251          return {offsetRemain:0}
252        }
253      } else if (offset < 0) {
254        if (this.listScroller.currentOffset().yOffset > 0) {
255          this.listScroller.scrollBy(0, offset)
256          return {offsetRemain:0}
257        } else if (this.scroller.currentOffset().yOffset <= 0) {
258          this.webController.scrollBy(0, offset)
259          return {offsetRemain:0}
260        }
261      }
262      return {offsetRemain:offset}
263    })
264  }
265}
266```
267HTML file to be loaded:
268
269```html
270<!-- index.html -->
271<!DOCTYPE html>
272<html>
273<head>
274    <meta name="viewport" id="viewport" content="width=device-width, initial-scale=1.0">
275    <style>
276        .blue {
277          background-color: lightblue;
278        }
279        .green {
280          background-color: lightgreen;
281        }
282        .blue, .green {
283         font-size:16px;
284         height:200px;
285         text-align: center;       /* Horizontally centered */
286         line-height: 200px;       /* Vertically centered (the height matches the container height) */
287        }
288    </style>
289</head>
290<body>
291<div class="blue" >webArea</div>
292<div class="green">webArea</div>
293<div class="blue">webArea</div>
294<div class="green">webArea</div>
295<div class="blue">webArea</div>
296<div class="green">webArea</div>
297<div class="blue">webArea</div>
298</body>
299</html>
300```
301![web-nested-scrolling](figures/web-gesture-scrolling.gif)
302<!--RP1--><!--RP1End-->
303