构建滚动进度指示器

Web 功能: 滚动条驱动的动画

滚动进度指示器是一种常见的界面模式,可直观地传达用户在可滚动文档或容器中的进度。当用户滚动时,视觉元素会更新以反映其位置,从而清晰直观地显示已查看的内容量和剩余内容量。

如何实现

如需创建滚动进度指示器,您需要准备两样东西:

  1. 用作进度条的元素。此元素通常为 position: fixed 或 position: absolute,以便在用户滚动时保持在视野范围内。
  2. 与滚动位置相关联的动画。

具体方法如下:

  • 首先,创建一个 HTML 元素作为进度条。您可以根据自己的喜好设置此元素的样式。
  • 接下来,在 CSS 中定义一个 @keyframes 动画,用于缩放进度条。一种常见方法是将元素从 scaleX(0) 缩放到 scaleX(1)。
  • 最后,将此动画应用到进度条元素,并将其 animation-timeline 设置为 scroll-timeline。这会指示浏览器根据最近的祖先滚动条的滚动位置来驱动动画进度。

示例代码

此代码使用由 scroll() 函数创建的匿名滚动时间轴,在滚动时放大 #progress 元素。

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: scroll())) {
    @keyframes grow-progress {
      from { transform: scaleX(0); }
      to { transform: scaleX(1); }
    }

    #progress {
      position: fixed;
      left: 0; top: 0;
      width: 100%; height: 1em;
      background: red;

      transform-origin: 0 50%;
      animation: grow-progress auto linear;
      animation-timeline: scroll();
    }
  }
}

由于 scroll() 函数在 DOM 中的位置,它将跟踪 block 方向上最近的祖先滚动条,此处为根滚动条。

<body>
  <!-- MANDATORY: Purely decorative visual scroll progress bars MUST set aria-hidden="true" to remove the empty element from the assistive technology reading tree -->
  <div id="progress" aria-hidden="true"></div>
</body>

此代码使用由 scroll-timeline 属性创建的命名 scroll-timeline,在滚动时放大 #progress 元素。

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: scroll())) {
    @keyframes grow-progress {
      from { transform: scaleX(0); }
      to { transform: scaleX(1); }
    }

    :root {
      scroll-timeline: --tl block;
    }

    #progress {
      position: fixed;
      left: 0; top: 0;
      width: 100%; height: 1em;
      background: red;

      transform-origin: 0 50%;
      animation: grow-progress auto linear;
      animation-timeline: --tl;
    }
  }
}

最佳做法

使用滚动驱动动画时,请务必遵循一些最佳实践,以确保提供流畅且无障碍的体验:

  • 务必添加功能检测:并非所有浏览器都支持滚动驱动的动画。使用 @supports (animation-timeline: scroll()) 检查是否支持,并为不支持的浏览器提供后备方案。
    • 请勿将 scroll-timeline-polyfill 软件包用于回退策略,因为它功能不完整,并且存在许多已知问题。
    • 如果动画仅被视为装饰性元素,请选择渐进增强,不要提供后备。
  • 务必从辅助技术朗读流程中移除纯装饰性元素:对纯视觉滚动指示器应用 aria-hidden="true",以确保屏幕阅读器不会遇到未命名的空节点。
  • 务必尊重用户偏好:有些用户更喜欢在网页上看到较少的动画。使用 prefers-reduced-motion 媒体查询为这些用户停用或减少动画。
  • 务必尝试仅为高性能 CSS 属性添加动画效果:为了获得最流畅的动画效果,请坚持为可由浏览器的合成器线程处理的属性(例如 transform 和 opacity)添加动画效果。为其他属性(例如 width 或 height)添加动画效果可能会导致性能问题。
  • 务必使用正确的声明顺序:使用 animation 简写属性时,请在其后声明 animation-timeline,以防止简写属性重置时间轴。

使用 scroll() 函数创建滚动驱动的动画时:

  • 可选:明确指定滚动条。如果目标不是最近的祖先滚动条,请使用 scroll(root) 或 scroll(self) 明确指定要使用的滚动条。
    • 如果 root、nearest 或 self 不够用,请使用命名的 scroll-timeline。
  • 可选:明确指定要跟踪的轴。如果未定位到默认的 block 轴(例如在水平滚动条中),请使用 scroll(block) 或 scroll(inline) 明确指定要跟踪的轴。

使用 scroll-timeline 属性创建滚动驱动的动画时:

  • 务必使用 CSS <dashed-ident> 作为名称。
  • 可选:明确指定要跟踪的轴。如果目标不是默认的 block 轴(例如在水平滚动条中),请使用 scroll-timeline-axis 明确指定要跟踪的轴。
  • 务必确保查找范围有效:如果声明 scroll-timeline 的元素不是动画元素的平面树祖先,请在共享祖先上使用 timeline-scope 来提升 scroll-timeline 名称的可见性。

后备策略

对于不支持滚动驱动动画的浏览器,您可以使用后备方案来重现视觉效果。后备方案通常使用滚动监听器(用于 ScrollTimeline 效果)或 IntersectionObserver API(用于 ViewTimeline 效果)构建。

在内置支持滚动驱动动画的浏览器中,请始终使用原生 CSS 实现,因为这些实现的性能更高。

请注意,并非所有效果都可以通过后备方法重现。

具体而言,对于此用例,以下脚本会为不支持滚动驱动动画的浏览器应用回退。它使用滚动监听器来跟踪根元素的滚动位置,并相应地更新进度条的 transform 属性。

<script>
  if (!CSS.supports('animation-timeline', 'scroll()')) {
    const progress = document.querySelector('#progress');

    window.addEventListener('scroll', () => {
      const scrollable = document.documentElement.scrollHeight - window.innerHeight;
      const scrolled = window.scrollY;
      const progressPercentage = (scrolled / scrollable);

      progress.style.transform = `scaleX(${progressPercentage})`;
    });
  }
</script>