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

如何实现带scroll snap的横向滚动容器点击按钮500ms滚动指定距离

实现自定义时长的平滑横向滚动

问题说明

现有配置了scroll-snap属性的横向可滚动容器,要求点击按钮时,容器以500ms动画时长精确滚动340px(对应320px子元素+20px间距),实现无延迟的平滑滚动效果。

解决方案

1. 修正CSS基础样式

先调整子元素宽度匹配需求,同时移除原生scroll-behavior: smooth(它无法自定义动画时长,会干扰自定义逻辑):

.Container {
  display: flex;
  scroll-snap-type: x mandatory;
  position: relative;
  overflow-y: hidden;
  overflow-x: scroll;
  gap: 20px;
  /* 移除原生平滑滚动,避免冲突 */
}

.Container>div {
  min-width: 320px;
  max-width: 320px;
  height: 100px;
  background-color: blue;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

2. 编写JS自定义滚动动画

使用requestAnimationFrame实现精确的时长控制和流畅动画,同时加入缓动函数让滚动更自然:

const container = document.querySelector('.Container');
const slideBtn = document.getElementById('slider_button');
const scrollDistance = 340; // 单次滚动距离
const duration = 500; // 动画时长(ms)

slideBtn.addEventListener('click', () => {
  const startScrollLeft = container.scrollLeft;
  const targetScrollLeft = startScrollLeft + scrollDistance;
  let startTime = null;

  const animateScroll = (timestamp) => {
    if (!startTime) startTime = timestamp;
    // 计算动画进度(0到1)
    const progress = Math.min((timestamp - startTime) / duration, 1);
    
    // 加入ease-in-out缓动,让动画更顺滑
    const easeProgress = progress < 0.5 
      ? 2 * progress * progress 
      : 1 - Math.pow(-2 * progress + 2, 2) / 2;
    
    // 更新滚动位置
    container.scrollLeft = startScrollLeft + (targetScrollLeft - startScrollLeft) * easeProgress;
    
    // 未完成则继续动画
    if (progress < 1) {
      requestAnimationFrame(animateScroll);
    }
  };

  requestAnimationFrame(animateScroll);
});

3. 关键细节说明

  • 用requestAnimationFrame绑定浏览器刷新周期,避免卡顿和延迟
  • 自定义缓动函数让滚动动画更符合用户视觉预期
  • 精确控制滚动距离,同时兼容scroll-snap的吸附特性(滚动结束后会自动对齐到子元素)
  • 移除原生scroll-behavior,防止默认平滑逻辑干扰自定义动画

完整可运行代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <style>
    .Container {
      display: flex;
      scroll-snap-type: x mandatory;
      position: relative;
      overflow-y: hidden;
      overflow-x: scroll;
      gap: 20px;
      padding: 10px 0;
    }

    .Container>div {
      min-width: 320px;
      max-width: 320px;
      height: 100px;
      background-color: blue;
      scroll-snap-align: start;
      scroll-snap-stop: always;
      border-radius: 8px;
    }

    #slider_button {
      margin-top: 20px;
      padding: 8px 16px;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <div class="Container">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>
  <button id="slider_button">Slide</button>

  <script>
    const container = document.querySelector('.Container');
    const slideBtn = document.getElementById('slider_button');
    const scrollDistance = 340;
    const duration = 500;

    slideBtn.addEventListener('click', () => {
      const startScrollLeft = container.scrollLeft;
      const targetScrollLeft = startScrollLeft + scrollDistance;
      let startTime = null;

      const animateScroll = (timestamp) => {
        if (!startTime) startTime = timestamp;
        const progress = Math.min((timestamp - startTime) / duration, 1);
        const easeProgress = progress < 0.5 
          ? 2 * progress * progress 
          : 1 - Math.pow(-2 * progress + 2, 2) / 2;
        
        container.scrollLeft = startScrollLeft + (targetScrollLeft - startScrollLeft) * easeProgress;
        
        if (progress < 1) {
          requestAnimationFrame(animateScroll);
        }
      };

      requestAnimationFrame(animateScroll);
    });
  </script>
</body>
</html>

内容的提问来源于stack exchange,提问作者cryndry

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 00:09:23