滚动吸附(Scroll Snap)

你见过商品页的卡片图吗?每次切换卡片自动居中,现在已经可以通过 CSS 实现了,无需 JavaScript。

CSS 滚动吸附模块可以定义用户在滚动浏览文档过程中,使滚动子元素自动对齐可视区域的某些位置。例如:多张卡片预览元素滚动自动居中。

又比如,产品官网首页对产品特点描述时,往往采用卡片式的介绍,而这些卡片通常近似整个屏幕大小,每一张卡片通过向下滑动的方式切换。例如第一张卡片介绍产品的用户定位,当鼠标滚轮向下滚动时,第二张介绍卡片展示商品拥有哪些优势。同时结合容器状态查询滚动驱动动画,在卡片之间切换时可以有些炫酷的切入动画。请注意,我没有提到任何有关 ECMAScript 的事情。

参考资料

  1. 滚动捕捉的基本概念 - MDN
  2. 滚动吸附 - MDN
  3. 利用 CSS Scroll Snap 有效控制滚动 - web.dev

前言

滚动吸附的效果首先得有滚动的容器才能吸附,因此一定具备一个可以滚动的容器,容器才好捕获滚动的元素。滚动吸附模块分为对容器和对子元素的属性。

必须设置的属性

要实现吸附效果,至少需要包含 3 个属性:

属性作用应用于
overflow有滚动才有吸附容器
scroll-snap-type声明吸附的轴和吸附严格度容器
scroll-snap-align子元素如何对齐容器可视区域的吸附位置子元素

针对滚动容器

滚动吸附模块针对滚动容器的属性包括:

  1. overflow:必须将此属性设置为可以滚动
  2. scroll-snap-type:使用此属性决定是否开启子元素吸附。此属性包含吸附的轴和吸附元素时的宽容度
  3. scroll-padding:声明容器在吸附子元素时预留的内边距

针对容器子元素

针对容器子元素的属性包括:

  1. scroll-snap-align:定义滚动容器子元素吸附在滚动轴可视区域的开始、中间还是结束位置
  2. scroll-snap-stop:定义滚动容器的子元素是否每次滚动强制停留到自身。这是出于用户有可能快速滚动时忽略了中间的重要信息。如果设置为 always 则表示每张卡片强制停留并预览
  3. scroll-margin:子元素在距离滚动容器内可视区域边界的外边距

实例详解

大多数元素在官方都有案例提供,但还是不够丰富。我将针对一些不太容易明显体会出区别的属性做出详细案例和说明。

scroll-snap-type

语法:

scroll-snap-type: <x | y | block | inline | both> <mandatory | proximity>;

scroll-snap-type 属性需要知道在哪个方向上有滚动吸附。方向可以是 xy 或者逻辑对应关系 blockinline,还可以用关键字 both 使两个轴都有滚动吸附。

此属性除了可以声明吸附的轴以外,还可以说明吸附的宽松程度,通常放在第二个参数上:

  • mandatory:声明了子元素被捕获点捕获
  • proximity(默认值):和 mandatory 区别在于,子元素是否被其设置的捕获点对齐取决于浏览器的实现。通常情况下,该值的默认行为是:如果一个滚动元素的边框没有位于捕获点内时,它就不会被捕获对齐

HTML 示例:

<section
  class="snap-type-page__scroller snap-type-page__scroller--mandatory"
  title="snap-mandatory"
>
  <div><span>100px</span></div>
  <div><span>100px</span></div>
  <div><span>100px</span></div>
</section>

<section
  class="snap-type-page__scroller snap-type-page__scroller--proximity"
  title="snap-proximity"
>
  <div><span>100px</span></div>
  <div><span>100px</span></div>
  <div><span>100px</span></div>
</section>

CSS 示例:

这里针对两个不同捕获严格程度的 <section>,通过 CSS 对它们进行不同的声明。这里必须设置 overflow,否则滚动吸附就没有意义。

.snap-type-page__scroller {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
  overflow-y: auto;
}

/* 第一个参数 y:纵向吸附;第二个参数 mandatory — 滚动结束必须落在吸附点 */
.snap-type-page__scroller--mandatory {
  scroll-snap-type: y mandatory;
}

/* 第二个参数 proximity — 仅当停止位置靠近吸附点时才对齐 */
.snap-type-page__scroller--proximity {
  scroll-snap-type: y proximity;
}

如下案例,灰色的虚线标示所有子元素捕获的辅助线。可以将元素块与捕获辅助线保持距离,就能体会到具体的区别。

注意:假设强制声明子元素必须对齐捕获点(也就是设置 mandatory 参数),需要避免以下问题:假设某个子元素非常大,如果严格按照吸附位置严格对齐,那么将会导致超出滚动可视区域部分不可见。当用户想滚动去查看超出可视区域的内容时,因为强制捕获对齐的关系,用户滚动的位置又会被吸附获取,造成内容再次不可见,会对用户造成很大的困扰。此属性只有确保你的滚动子元素不会在捕获点产生溢出时使用,以增强对交互效果的可预测性。


scroll-snap-align

此属性用于标明:子元素如何对齐父容器滚动轴的可视区域位置。有效值包括 startendcenternone

HTML 示例:

<section class="snap-align-start">
  <div><span>start</span></div>
  <div><span>start</span></div>
  <div><span>start</span></div>
</section>

<section class="snap-align-center">
  <div><span>center</span></div>
  <div><span>center</span></div>
  <div><span>center</span></div>
</section>

<section class="snap-align-end">
  <div><span>end</span></div>
  <div><span>end</span></div>
  <div><span>end</span></div>
</section>

CSS 示例:

在下列 CSS 示例中,给不同的 <section> 设置不同的 scroll-snap-align 值,以查看不同的对齐位置。

section {
  height: 100px;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
}

.snap-align-start > div {
  scroll-snap-align: start;
}

.snap-align-center > div {
  scroll-snap-align: center;
}

.snap-align-end > div {
  scroll-snap-align: end;
}

可以滚动下列不同的滚动条,体会不同 scroll-snap-align 的值的不同对齐行为。

特性前瞻

注意:现在(2026 年 3 月 22 日),对于滚动所附加的伪元素部分,还未被主流浏览器所支持,请谨慎使用。

  1. ::scroll-button:滚动按钮元素
  2. ::scroll-marker:滚动元素的标记
  3. ::scroll-marker-group:所有滚动元素的标记容器