使用 CSS Scroll Snap 实现良好控制的滚动

通过声明滚动捕捉位置,创建良好控制的滚动体验。

CSS Scroll Snap 功能允许 Web 开发者通过声明滚动捕捉位置来创建良好控制的滚动体验。分页文章和图像轮播是两个常用的示例。CSS Scroll Snap 提供了一个易于使用且一致的 API,用于构建这些流行的 UX 模式。

背景

滚动捕捉的用例

滚动是与 Web 内容交互的一种流行且自然的方式。它是平台提供对屏幕上一次可见的更多信息的原生方式,在屏幕空间有限的移动平台上尤其重要。因此,Web 作者越来越倾向于将内容组织成可滚动的平面列表,而不是深层层级结构,这不足为奇。

滚动的主要缺点是缺乏精确性。滚动很少最终与段落或句子对齐。对于具有有意义边界的分页或条目化内容,当滚动在页面或图像的中间结束时,这种情况更加明显,导致其部分可见。这些用例受益于良好控制的滚动体验。

长期以来,Web 开发者一直依赖于基于 JavaScript 的解决方案来控制滚动,以帮助解决这一缺点。但是,由于缺乏滚动自定义原语或对合成滚动的访问,基于 JavaScript 的解决方案无法提供完全保真的解决方案。CSS Scroll Snap 确保了一个快速、高保真且易于使用的解决方案,该解决方案在所有浏览器中都能一致地工作。

CSS Scroll Snap 允许 Web 作者为每个滚动容器标记滚动操作的边界,以便在这些边界处完成滚动。然后,浏览器根据滚动操作的细节、滚动容器的布局和可见性以及捕捉位置的细节,选择最合适的结束位置,然后平滑地动画到该位置。回到我们之前的示例,当用户完成轮播滚动时,其可见图像会捕捉到位。无需 JavaScript 进行滚动调整。

Example of using css scroll snap with an image carousel.
使用 CSS Scroll Snap 的图像轮播示例。在此示例中,滚动捕捉确保滚动结束时,图像水平中心与滚动容器的水平中心对齐。

CSS Scroll Snap

滚动捕捉是指在滚动操作完成后,调整滚动容器的滚动偏移量到首选的捕捉位置

滚动容器可以通过使用 scroll-snap-type 属性选择加入滚动捕捉。这告诉浏览器,它应该考虑将此滚动容器捕捉到其后代产生的捕捉位置。scroll-snap-type 确定滚动的轴:xyboth,以及捕捉的严格程度:mandatoryproximity。稍后详细介绍这些。

可以通过在元素上声明所需的对齐方式来生成捕捉位置。此位置是最近的祖先滚动容器和元素按照给定轴指定的对齐方式对齐时的滚动偏移量。以下对齐方式在每个轴上都是可能的:startendcenter

start 对齐方式意味着滚动容器捕捉端口的起始边缘应与元素捕捉区域的起始边缘齐平。类似地,endcenter 对齐方式意味着滚动容器捕捉端口的结束边缘或中心应与元素捕捉区域的结束边缘或中心齐平。

水平滚动轴上各种对齐方式的示例。

以下示例说明了如何使用这些概念。

滚动捕捉的常见用例是图像轮播。例如,要创建一个水平图像轮播,该轮播在您滚动时捕捉到每个图像,我们可以将滚动容器指定为在水平轴上具有强制性的 scroll-snap-type。将每个图像设置为 scroll-snap-align: center,以确保捕捉将图像居中在轮播中。

#gallery {
  scroll-snap-type: x mandatory;
  overflow-x: scroll;
  display: flex;
}

#gallery img {
   scroll-snap-align: center;
}
<div id="gallery">
  <img src="cat.jpg">
  <img src="dog.jpg">
  <img src="another_cute_animal.jpg">
</div>

由于捕捉位置与元素相关联,因此捕捉算法可以智能地决定何时以及如何捕捉,具体取决于元素和滚动容器的大小。例如,考虑一个图像大于轮播的情况。幼稚的捕捉算法可能会阻止用户平移以查看完整图像。但是 规范 要求实现检测这种情况,并允许用户在该图像内自由滚动,仅在其边缘捕捉。

查看演示 | 源代码

示例:旅程式产品页面

另一个可以从滚动捕捉中受益的常见案例是具有多个逻辑部分的页面,用于垂直滚动浏览,例如,典型的产品页面。scroll-snap-type: y proximity; 更适合此类情况。当用户滚动到特定部分的中间时,它不会干扰,但在用户滚动足够靠近新部分时,它也会捕捉并将注意力吸引到新部分。

以下是如何实现此目的的方法

article {
  scroll-snap-type: y proximity;
  /* Reserve space for header plus some extra space for sneak peeking. */
  scroll-padding-top: 15vh;
  overflow-y: scroll;
}
section {
  /* Snap align start. */
  scroll-snap-align: start;
}
header {
  position: fixed;
  height: 10vh;
}
<article>
  <header> Header </header>
  <section> Section One </section>
  <section> Section Two </section>
  <section> Section Three </section>
</article>

滚动内边距和外边距

产品页面具有固定位置的顶部标题。设计还要求在滚动容器捕捉到位时,顶部部分保留一些可见性,以便为用户提供有关上方内容的设计提示。

scroll-padding 属性是一个新的 CSS 属性,可用于调整滚动容器的有效可查看区域或捕捉端口,该捕捉端口在计算滚动捕捉对齐方式时使用。该属性定义了相对于滚动容器的内边距框的内插。在我们的示例中,顶部添加了 15vh 的额外内插,这指示浏览器将较低的位置(15vh 低于滚动容器的顶部边缘)视为其垂直起始边缘以进行滚动捕捉。捕捉时,捕捉目标元素的起始边缘将与此新位置齐平,从而在上方留下空间。

scroll-margin 属性定义了外插量,用于调整捕捉目标有效框,类似于 scroll-padding 在捕捉滚动容器上的功能。

您可能已经注意到,这两个属性中都没有单词“snap”。这是有意的,因为它们实际上修改了所有相关滚动操作的框,而不仅仅是滚动捕捉。例如,Chrome 在计算分页滚动操作(如 PageDown 和 PageUp)的页面大小时,以及在计算 Element.scrollIntoView() 操作的滚动量时,都考虑了它们。

查看演示 | 源代码

与其他滚动 API 的交互

DOM 滚动 API

滚动捕捉发生在所有滚动操作之后,包括脚本发起的滚动操作。当您使用诸如 Element.scrollTo 之类的 API 时,浏览器将计算操作的预期滚动位置,然后应用适当的捕捉逻辑以找到最终捕捉位置。因此,用户脚本无需为捕捉进行任何手动计算。

平滑滚动

平滑滚动控制程序化滚动操作的行为,而滚动捕捉确定其目的地。由于它们控制滚动的正交方面,因此可以一起使用并相互补充。

滚动溢出行为

滚动溢出行为 API 控制滚动如何在多个元素之间链接,并且不受滚动捕捉的影响。

注意事项和最佳实践

避免在目标元素间隔很远时使用强制捕捉。这可能会导致捕捉位置之间的内容无法访问。

在许多情况下,可以添加滚动捕捉作为增强功能,而无需进行功能检测。如果需要,请使用 @supportsCSS.supports 来检测对 CSS Scroll Snap 的支持。避免使用 scroll-snap-type,它也存在于已弃用的规范中。

CSS 中的功能检测

@supports (scroll-snap-align: start) {
  article {
    scroll-snap-type: y proximity;
    scroll-padding-top: 15vh;
    overflow-y: scroll;
  }
}

JavaScript 中的功能检测

if (CSS.supports('scroll-snap-align: start')) {
  // use css scroll snap
} else {
  // use fallback
}

不要假设诸如 Element.scrollTo 之类的程序化滚动 API 始终在请求的滚动偏移量处完成。滚动捕捉可能会在程序化滚动完成后调整滚动偏移量。请注意,即使在滚动捕捉之前,这也不是一个好的假设,因为滚动可能会因其他原因而中断,但滚动捕捉尤其如此。

未来工作

滚动体验是 Chrome 团队最近一项调查 的重点。调查结果确定了几个需要额外工作的领域,以缩小插件库和 CSS 之间的差距。即将到来的工作将侧重于 scroll-snap,包括

  1. API 可用性和跨浏览器兼容性。
  2. 关于 新的 CSS API(如 scroll-start)的工作。
  3. 关于 新的 JS 事件(如 snapChanged())的工作。