如何防止触摸滚动方向锁定 垂直滚动区内嵌水平滚动区适配方案
嵌套滚动容器方向锁问题解决方案
实现原理
浏览器默认的滚动方向锁定机制,会在手势初期根据短距离滑动的偏移量判定滚动方向,一旦判定就锁定对应滚动容器直到手势结束,这是导致误判需要抬手重滑的核心原因。
通过主动在手势初期拦截触摸事件,自行判定滑动方向后,通过touch-action属性控制对应容器的滚动响应权限,即可完全规避系统默认的误判问题,同时保留原生滚动的惯性、回弹等全部原生体验,不需要用JS模拟滚动逻辑,性能和原生滚动一致。
前置样式约束
首先给两层滚动容器设置基础的滚动属性和触摸响应规则,从CSS层面先做第一层约束:
/* 外层纵向滚动容器 */ .outer-scroll-wrapper { width: 100%; height: 100vh; overflow-y: auto; overflow-x: hidden; touch-action: pan-y; /* iOS端开启惯性滚动 */ -webkit-overflow-scrolling: touch; } /* 内层横向滚动行 */ .horizontal-scroll-row { width: 100%; overflow-x: auto; overflow-y: hidden; white-space: nowrap; touch-action: pan-x; -webkit-overflow-scrolling: touch; }
交互逻辑实现
通过轻量的JS逻辑在手势阶段判定方向,动态调整容器的touch-action属性,锁定正确的滚动响应容器:
// 触摸状态缓存 const touchStatus = { startX: 0, startY: 0, directionLocked: false, currentActiveRow: null } // 方向判定阈值,单位px,8px为适配大多数场景的经验值 const DIRECTION_JUDGE_THRESHOLD = 8 const outerWrapper = document.querySelector('.outer-scroll-wrapper') // 给所有横向滚动行绑定触摸事件 document.querySelectorAll('.horizontal-scroll-row').forEach(row => { row.addEventListener('touchstart', (e) => { const touchPoint = e.touches[0] touchStatus.startX = touchPoint.clientX touchStatus.startY = touchPoint.clientY touchStatus.directionLocked = false touchStatus.currentActiveRow = row // 重置两个容器的触摸响应权限 outerWrapper.style.touchAction = 'pan-y' row.style.touchAction = 'pan-x' }, { passive: true }) row.addEventListener('touchmove', (e) => { // 方向已经锁定后不再重复判定 if (touchStatus.directionLocked) return const touchPoint = e.touches[0] const deltaX = Math.abs(touchPoint.clientX - touchStatus.startX) const deltaY = Math.abs(touchPoint.clientY - touchStatus.startY) // 滑动距离未到阈值,暂不判定方向 if (Math.max(deltaX, deltaY) < DIRECTION_JUDGE_THRESHOLD) return touchStatus.directionLocked = true if (deltaY > deltaX) { // 判定为纵向滑动,禁用当前行的横向响应,全部交予外层容器 touchStatus.currentActiveRow.style.touchAction = 'none' } else { // 判定为横向滑动,禁用外层容器的纵向响应,全部交予当前行 outerWrapper.style.touchAction = 'none' } }, { passive: true }) row.addEventListener('touchend', () => { // 手势结束重置所有状态 touchStatus.currentActiveRow = null touchStatus.directionLocked = false outerWrapper.style.touchAction = 'pan-y' }) })
调优注意事项
- 判定阈值不要设置过高,否则会出现滑动初期跟手迟滞;也不要设置过低,否则手指轻微的抖动就会导致方向误判,可根据自身产品的交互手感在6~12px区间调整
- 不要通过
preventDefault拦截事件后手动修改scrollTop/scrollLeft模拟滚动,这种方式会丢失原生滚动的惯性、回弹、跟手度,性能也远差于原生滚动 - 如果需要适配PC端鼠标滚轮场景,可额外监听
wheel事件,根据deltaX和deltaY的比值做相同的方向判定逻辑即可
内容的提问来源于stack exchange,提问作者Torge
相关产品推荐
相关产品推荐

