如何实现移动端滚动时间选择器多列同时平滑触摸滚动
移动端滚动时间选择器滚动中断问题解决
问题重现
移动端Web实现的滚动时间选择器,基于scroll-snap实现列内元素垂直居中停靠,但存在交互问题:当滚动某一列后立即触摸滚动另一列时,初始滚动的列会瞬间停止,用户无法清晰确认选中值。
原实现的列结构代码:
<div style="overflow: scroll; scroll-snap-type: y mandatory; -webkit-overflow-scrolling: touch;"> <p></p> <!-- 缓冲元素 --> <p></p> <!-- 缓冲元素 --> <p style="scroll-snap-align: center;">0h</p> <p style="scroll-snap-align: center;">1h</p> <p style="scroll-snap-align: center;">2h</p> <p style="scroll-snap-align: center;">3h</p> <p style="scroll-snap-align: center;">4h</p> <!-- 更多时间项 --> <p></p> <!-- 缓冲元素 --> <p></p> <!-- 缓冲元素 --> </div>
注:原代码中容器的scroll-snap-align: y mandatory为错误写法,正确应为scroll-snap-type: y mandatory,子元素使用scroll-snap-align: center
问题原因
scroll-snap-type: y mandatory本应让容器滚动后自然停靠到子元素的居中位置,但当触发其他列的touchstart/touchmove事件时,浏览器会中断当前列的滚动上下文,抢占触摸事件处理权,导致初始列的滚动戛然而止,跳过自然停靠过程。
解决方案
核心思路:仅当某一列正在滚动时,临时禁用其他列的触摸事件,待滚动完全停止后恢复其可滚动状态。
1. 调整HTML结构
给所有滚动列添加统一类名,方便批量操作:
<div class="time-scroll-column" style="overflow: scroll; scroll-snap-type: y mandatory; -webkit-overflow-scrolling: touch;"> <p></p> <!-- 缓冲元素 --> <p></p> <!-- 缓冲元素 --> <p style="scroll-snap-align: center;">0h</p> <p style="scroll-snap-align: center;">1h</p> <p style="scroll-snap-align: center;">2h</p> <p style="scroll-snap-align: center;">3h</p> <p style="scroll-snap-align: center;">4h</p> <!-- 更多时间项 --> <p></p> <!-- 缓冲元素 --> <p></p> <!-- 缓冲元素 --> </div> <!-- 其他时间列(如分钟列)复制上述结构 -->
2. JavaScript逻辑实现
const timeColumns = document.querySelectorAll('.time-scroll-column'); let activeScrollColumn = null; let scrollEndTimer = null; // 标记当前滚动列,禁用其他列的触摸响应 function markActiveColumn(e) { activeScrollColumn = e.target; timeColumns.forEach(col => { if (col !== activeScrollColumn) { col.classList.add('is-scroll-disabled'); } }); clearTimeout(scrollEndTimer); } // 滚动结束后恢复其他列的可滚动状态 function releaseActiveColumn() { scrollEndTimer = setTimeout(() => { activeScrollColumn = null; timeColumns.forEach(col => { col.classList.remove('is-scroll-disabled'); }); }, 100); // 延迟100ms确保滚动完全停止,可根据实际调整 } // 绑定事件到所有列 timeColumns.forEach(column => { // 监听滚动事件,标记当前活跃列 column.addEventListener('scroll', markActiveColumn); // 监听触摸结束/取消事件,判断滚动终止 column.addEventListener('touchend', releaseActiveColumn); column.addEventListener('touchcancel', releaseActiveColumn); // 触摸事件拦截:仅当列未被禁用时允许滚动 column.addEventListener('touchstart', (e) => { if (column.classList.contains('is-scroll-disabled')) { e.preventDefault(); } }, { passive: false }); column.addEventListener('touchmove', (e) => { if (column.classList.contains('is-scroll-disabled')) { e.preventDefault(); } }, { passive: false }); });
方案说明
- 通过
scroll事件实时检测当前正在滚动的列,给其他列添加禁用类 - 禁用类触发时,拦截该列的
touchstart/touchmove事件,阻止其抢占滚动上下文 - 滚动结束(
touchend/touchcancel)后延迟恢复其他列的可滚动状态,避免滚动未完全停止时的误触 - 保留了
-webkit-overflow-scrolling: touch保证移动端的弹性滚动体验
内容的提问来源于stack exchange,提问作者epic tris
相关产品推荐
相关产品推荐

