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

CSS scroll-snap如何实现每次滑动仅切换一个区块 不受滑动速度影响

问题说明

水平滚动容器应用CSS scroll-snap 属性后,快速向右滑动时会直接从第一个区块跳过第二个区块,停在第三个区块,需要实现无论滑动速度快慢,每次滑动仅切换一个区块的效果。

原始复现代码如下:

.slider {
  border: 4px solid black;
  width: 300px;
  height: 200px;
  overflow: auto;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  grid-template-columns: repeat(10, 100%);
  scroll-snap-type: both mandatory;
}

.slider::-webkit-scrollbar {
  height: 0;
}

.slds {
  color: black;
  width: 100%;
  height: 100%;
  list-style: none;
  font-size: 30px;
  scroll-snap-align: center;
}

.slds:nth-child(1){background: red;}
.slds:nth-child(2){background: orange;}
.slds:nth-child(3){background: green;}
<div class="slider">
  <div class="slds">1</div>
  <div class="slds">2</div>
  <div class="slds">3</div>
</div>
原因分析

原生scroll-snap默认逻辑是根据滑动结束时的动量、最终停留位置计算距离最近的吸附点,没有默认限制单次滑动的跨越步长,因此大力度快速滑动时会直接跨过中间区块。

解决方案

方案1:纯CSS实现(适配现代浏览器)

如果不需要兼容Safari 14及以下版本,直接给吸附子项添加scroll-snap-stop: always属性即可,该属性会强制滚动过程中不能跳过任何吸附点,滑动时无论速度多快,都会停在经过的第一个吸附位置。
修改后的.slds样式如下:

.slds {
  color: black;
  width: 100%;
  height: 100%;
  list-style: none;
  font-size: 30px;
  scroll-snap-align: center;
  scroll-snap-stop: always; /* 新增该行 */
}

同时建议将.slider的scroll-snap-type从both mandatory改为x mandatory,因为当前容器是水平滚动,不需要纵向吸附逻辑,避免不必要的计算。

方案2:JS辅助实现(全版本兼容)

如果需要兼容更低版本浏览器,可以通过JS拦截滚动逻辑,强制每次滚动仅移动一个区块宽度:

  • 记录当前激活的区块索引,监听容器的滚动事件
  • 通过滚动偏移量判断滑动方向
  • 手动计算相邻区块的滚动位置,调用平滑滚动API移动到目标位置,滚动过程中临时锁定事件避免重复触发

完整实现代码如下:

/* CSS部分保留scroll-snap相关属性提升体验 */
.slider {
  border: 4px solid black;
  width: 300px;
  height: 200px;
  overflow: auto;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  grid-template-columns: repeat(3, 100%);
  scroll-snap-type: x mandatory;
  overscroll-behavior: contain;
}

.slider::-webkit-scrollbar {
  height: 0;
}

.slds {
  color: black;
  width: 100%;
  height: 100%;
  list-style: none;
  font-size: 30px;
  scroll-snap-align: center;
}

.slds:nth-child(1){background: red;}
.slds:nth-child(2){background: orange;}
.slds:nth-child(3){background: green;}
<div class="slider">
  <div class="slds">1</div>
  <div class="slds">2</div>
  <div class="slds">3</div>
</div>

<script>
const slider = document.querySelector('.slider')
const slideWidth = slider.clientWidth
let currentIndex = 0
let isScrolling = false

slider.addEventListener('scroll', () => {
  if (isScrolling) return
  isScrolling = true

  // 判断滚动方向计算目标索引
  const newIndex = Math.round(slider.scrollLeft / slideWidth)
  const targetIndex = newIndex > currentIndex ? currentIndex + 1 : currentIndex - 1
  // 边界判断避免越界
  const safeTargetIndex = Math.max(0, Math.min(targetIndex, slider.children.length - 1))
  
  // 平滑滚动到目标区块
  slider.scrollTo({
    left: safeTargetIndex * slideWidth,
    behavior: 'smooth'
  })

  currentIndex = safeTargetIndex
  // 滚动动画结束后解锁
  setTimeout(() => {
    isScrolling = false
  }, 300)
})
</script>

注意:JS实现中需要根据实际的滚动动画时长调整setTimeout的延迟时间,避免动画未结束就解锁导致滚动跳变。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 03:09:19