You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现移动端滚动时间选择器多列同时平滑触摸滚动

移动端滚动时间选择器滚动中断问题解决

问题重现

移动端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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 03:40:21