HarmonyOS 鸿蒙Next中当Scroll组件嵌套Web组件滑动时,滑动冲突,如何处理?
HarmonyOS 鸿蒙Next中当Scroll组件嵌套Web组件滑动时,滑动冲突,如何处理? 当Scroll组件嵌套Web组件滑动时,滑动冲突,如何处理?
【背景知识】
- Scroll:可滚动的容器组件,当子组件的布局尺寸超过父组件的尺寸时,内容可以滚动。
- ArkWeb:ArkWeb(方舟Web)提供了Web组件,用于在应用程序中显示Web页面内容。
- 触摸测试控制可以设置不同的触摸测试响应模式,影响组件的触摸测试收集结果,最终影响后续的触屏事件分发。
【解决方案】
Web组件和Scroll组件在同一页面时,会存在Web获取焦点,导致Scroll无法上下滑动。
解决思路是可以通过设置边界调节来屏蔽其中一个的滑动响应来实现避免冲突的效果。
方案一:屏蔽Web响应:
可以在Scroll的onScroll事件中增加判断,在Scroll滑动到底部之前屏蔽所有Web响应,不过需要注意的是,如果Web本身也需要滑动的话,该方法不太适用,这种场景下可以使用方案二来处理冲突。
代码示例如下:
private scroller: Scroller = new Scroller();
@State webEnable: boolean = false
Scroll(this.scroller){
Web().enable(this.webEnable)
}.onScroll(() => {
if (this.scroller.isAtEnd()) {
this.webEnable = true
} else {
this.webEnable = false
}
})
方案二:触摸事件传递:
可以使用触摸测试控制来规避此种情况,请给显示在上层的节点设置hitTestBehavior为HitTestMode.BLock。详情见参考文档。
代码示例如下:
Scroll(){
Web({ src:"index.html",controller: this.webviewController})
.width("100%")
.height(220)
}.hitTestBehavior(HitTestMode.Block)// 阻断触摸事件传递
方案三:调整嵌套滚动模式:
在嵌套滚动组件时,可以通过设置nestedScroll属性来控制滚动顺序。
Scroll() {
Web({ src: 'xxx'})
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST, // 向前滚动父容器优先
scrollBackward: NestedScrollMode.SELF_FIRST// 向后滚动子组件优先
})
}
.scrollable(ScrollDirection.Vertical)
【总结】
当两个可响应滑动的组件(如Web和Scroll)共存时,可能出现焦点抢占导致某一组件无法滑动的问题。
通过控制组件的交互响应权,避免冲突,主要有三类方案:
-
动态启用/禁用组件响应:
- 根据外部条件(如滚动位置)切换组件的交互状态,在需要某一组件响应时,禁用另一组件。
- 例:Scroll未滑到底部时禁用Web的交互,滑到底部后再启用Web。
-
控制触摸事件传递:
- 通过设置组件的触摸测试行为(如hitTestBehavior),让上层组件阻断事件向下传递,确保自身优先响应。
- 例:给Scroll设置阻断事件的属性,避免Web抢占触摸事件。
-
自定义滚动优先级:
- 为嵌套的滚动组件配置滚动顺序规则(如nestedScroll),明确父/子组件的滚动优先级,按规则依次响应。
- 例:向前滚动时父组件优先,向后滚动时子组件优先。
更多关于HarmonyOS 鸿蒙Next中当Scroll组件嵌套Web组件滑动时,滑动冲突,如何处理?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
-
使用Web组件nestedScroll属性来设置上下左右四个方向,或者设置向前、向后两个方向的嵌套滚动模式,实现与父组件的滚动联动,同时也允许在过程中动态改变嵌套滚动的模式。
-
Web组件嵌套滚动的典型应用场景为,在页面中,多个独立区域需进行滚动,当用户滚动Web区域内容时,可联动其他滚动区域,实现上下左右全方位滑动页面的嵌套滚动体验。内嵌于可滚动容器(Grid、List、Scroll、Swiper、Tabs、WaterFlow、Refresh、bindSheet)中的Web组件,接收到滑动手势事件后,需要设置ArkUI的NestedScrollMode枚举属性,实现Web组件与ArkUI可滚动容器的嵌套滚动。
-
参考代码:
// xxx.ets
import { webview } from '@kit.ArkWeb';
@Entry
@ComponentV2
struct NestedScroll {
private scrollerForScroll: Scroller = new Scroller()
private listScroller: Scroller = new Scroller()
controller: webview.WebviewController = new webview.WebviewController();
@Local arr: Array<number> = []
aboutToAppear(): void {
for (let i = 0; i < 10; i++) {
this.arr.push(i)
}
}
build() {
Scroll(this.scrollerForScroll) {
Column() {
Web({ src: $rawfile("index.html"), controller: this.controller })
.nestedScroll({
scrollUp: NestedScrollMode.PARENT_FIRST,//向上滚动父组件优先
scrollDown: NestedScrollMode.SELF_FIRST,//向下滚动子组件优先
}).height("100%")
Repeat<number>(this.arr)
.each((item: RepeatItem<number>) => {
Text("Scroll Area")
.width("100%")
.height("40%")
.backgroundColor(0X330000FF)
.fontSize(16)
.textAlign(TextAlign.Center)
})
}
}
}
}
配置嵌套滚动优先级
通过设置nestedScroll属性预定义父子组件滚动行为优先级:
Scroll() {
Web({
src: "https://example.com",
controller: this.webController
})
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST, // 向前滚动父容器优先
scrollBackward: NestedScrollMode.SELF_FIRST // 向后滚动子组件优先
})
}
.width('100%')
.height('100%')
该方案通过定义滚动方向的事件派发规则,解决父子组件手势冲突。
调整触摸事件传递
设置上层容器的触摸测试模式屏蔽子组件响应:
Scroll() {
Web({
src: "https://example.com",
controller: this.webController
})
.hitTestBehavior(HitTestMode.Block) // 屏蔽Web组件触摸事件
}
此方法适用于Web本身无需滑动交互的场景,强制父容器处理所有触摸事件。
禁用Web滚动并手动派发
禁用Web组件原生滚动:
this.webController.setScrollable(false, webview.ScrollType.EVENT);
拦截手势事件:
Web({...})
.onGestureRecognizerJudgeBegin((event) => {
return GestureJudgeResult.REJECT; // 禁止Web自带手势
})
手动派发滚动偏移量:
this.webController.scrollBy(0, offset);
此方案适合需要精确控制滚动行为的复杂场景。
楼主好,Web组件默认会优先捕获触摸事件,导致父Scroll组件无法响应滑动操作。Web组件的手势响应层级高于Scroll组件,垂直滑动时Web会拦截事件。
楼主可以使用下面的方法进行解决------
设置外层容器的hitTestBehavior属性,阻断事件传递:
Scroll() {
Web({ src: 'https://example.com' })
.width('100%')
.height(200)
}
.hitTestBehavior(HitTestMode.Block) // 关键设置
通过nestedScroll属性协调父子组件滚动行为:
Scroll() {
Web({ src: 'https://example.com' })
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST, // 父组件优先滚动
scrollBackward: NestedScrollMode.SELF_FIRST // 子组件优先回弹
})
禁用Web组件滚动能力,通过Web控制器关闭原生滚动:
private webController: webview.WebviewController = new webview.WebviewController()
Web({ controller: this.webController })
.onGestureRecognizerJudgeBegin(() => {
return GestureJudgeResult.REJECT // 禁止手势识别
})
.onScrollFrameBegin(() => {
this.webController.setScrollable(false) // 禁用滚动
})
在 HarmonyOS ArkUI 中,当 Scroll
组件嵌套 Web
组件出现滑动冲突时,核心原因是两者都可能响应滑动事件(外层 Scroll
处理容器滚动,内层 Web
组件可能包含网页自身的滚动内容),导致滑动行为混乱。解决思路是通过事件拦截、方向判断或动态控制滚动权限,明确滑动事件的处理者。以下是具体解决方案:
方案 1:禁用 Web 组件自身滚动(简单直接)
如果 Web
组件加载的网页内容不需要内部滚动(或可通过外层 Scroll
整体滚动),可直接禁用 Web
组件的滚动能力,让外层 Scroll
全权处理滑动。
实现方式:
通过 Web
组件的onLoadFinish
事件,注入 JavaScript 代码禁用网页的滚动样式,阻止 Web
组件自身响应滑动。
方案 2:按滑动方向分配滚动权(常见场景)
若 Web
组件需要纵向滚动,而外层 Scroll
需要横向滚动(或反之),可通过判断滑动方向,让对应组件响应事件。
实现方式:
通过外层 Scroll
的onTouch
事件监听滑动方向,若方向符合 Web
组件的滚动需求,则阻止事件向 Scroll
传递(让 Web
组件处理);否则由 Scroll
处理。
方案 3:基于 Web 组件滚动状态动态控制(复杂场景)
若 Web
组件和外层 Scroll
同方向滚动(如都需要纵向滚动),可通过监听 Web
组件的滚动位置,当 Web
组件滚动到顶部 / 底部时,再让外层 Scroll
接管滑动。
实现步骤:
- 监听
Web
组件的滚动事件,通过 JS 注入获取网页滚动位置; - 当
Web
组件滚动到顶部(scrollTop === 0
)且继续向上滑时,让外层Scroll
响应; - 当
Web
组件滚动到底部(scrollTop + clientHeight >= scrollHeight
)且继续向下滑时,让外层Scroll
响应; - 其他情况由
Web
组件处理滚动。
在鸿蒙Next中处理Scroll嵌套Web组件的滑动冲突,可在Web组件外层添加GestureGroup
,设置gestureGroup={[PanGesture.Priority.High]}
提升Web组件手势优先级。或使用onTouch
事件监听,通过event.stopPropagation()
阻止事件冒泡。也可调整Web组件的touchable
属性为false
,由Scroll组件统一处理滑动。
在HarmonyOS Next中处理Scroll嵌套Web组件的滑动冲突,可以通过以下方案解决:
- 使用手势拦截控制: 在Web组件外层添加手势拦截,通过onTouch事件判断滑动方向,动态控制父Scroll和子Web的滑动优先级。示例代码:
@State isWebScrollable: boolean = true
build() {
Scroll() {
Column() {
// 其他内容...
Web({ src: 'www.example.com' })
.onTouch((event) => {
if (event.type === TouchType.Move) {
// 根据滑动方向决定是否拦截
if (Math.abs(event.offsetY) > Math.abs(event.offsetX)) {
this.isWebScrollable = event.offsetY < 0 && !this.isWebTop()
}
}
})
}
}
.onScrollEdge((edge) => {
// 当Scroll到达边界时允许Web滑动
if (edge === Edge.Bottom || edge === Edge.Top) {
this.isWebScrollable = true
}
})
}
- 使用NestedScrollController: 通过嵌套滚动控制器协调两者滚动行为:
const nestedScrollController = new NestedScrollController()
build() {
Scroll(NestedScroll({ controller: nestedScrollController })) {
Web({ src: 'www.example.com' })
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
}
}
- 禁用Web组件默认滚动: 如果不需要Web内部滚动,可直接禁用:
Web({ src: 'www.example.com' })
.disableScroll(true)
选择方案时需考虑具体交互需求,方案1适合需要保留两者滚动但需协调的场景,方案2适合需要精细控制滚动顺序的情况,方案3适合只需外部滚动的情况。