Scroll progress animations in CSS. Learn how to link animations to the scroll progress of a container. A vibrant gradient behind artwork of computer graphic with code and a window with a scrollbar.

CSS 中的滚动进度动画

作者头像Michelle Barker阅读时间:7 分钟

与滚动相关的动画通常可以为网站增添一抹优雅,但长期以来一直是 JavaScript 的专属领域。现在,一项全新的规范正在实施,使我们能够使用 CSS 创建丰富的滚动驱动体验!

当我们想到滚动驱动的动画时,通常会想到两种情况之一

  • 当用户滚动时发生的动画,动画的进度与滚动进度明确关联。例如,一篇长文章的进度条。
  • 当元素进入、退出或穿过可见区域时,该元素上发生的动画——通常是视窗,但它也可以是另一个可滚动容器的可见部分(这被定义为滚动端口)。

滚动驱动动画规范涵盖了这两种类型的动画。在本文中,我们将首先看一下滚动进度时间线,顾名思义,它将动画链接到滚动的进度。

注意:本文中介绍的功能在撰写本文时浏览器支持有限。最好使用Chrome Canary,但您也可以在 Chrome 115 或更高版本中启用实验性功能,以跟随示例并自己尝试滚动关联动画。

使用动画时间线

在此示例中,我们将实现一个常见的功能:动画化一个简单的进度条,使其在用户滚动网页时从左到右缩放。因为我们要将动画链接到根滚动的进度,所以我们可以使用匿名滚动进度时间线。

首先,让我们定义动画本身。我们希望进度条从左到右缩放,所以我们将使用transform

css
@keyframes scaleProgress {
  0% {
    transform: scaleX(0);
  }
  100% {
    transform: scaleX(1);
  }
}

为了将进度条元素的动画与滚动的进度关联起来,我们使用了animation-timeline 属性,并将其值设置为scroll() 函数。

css
.progress {
  animation-timeline: scroll();
}

scroll() 函数允许我们指定滚动容器和轴。默认值为 scroll(nearest block),这意味着动画将与块轴上最近的可滚动祖先链接。这对于我们的目的已经足够了,尽管我们也可以选择将根指定为滚动容器,因为我们要明确地将动画链接到视窗的滚动进度。

css
.progress {
  animation-timeline: scroll(root block);
}

最后,我们需要将动画添加到进度条元素中,使用我们的关键帧动画作为animation-name。我们需要将动画持续时间设置为 auto,因为持续时间将由滚动进度决定。我们还将缓动(animation-timing-function)设置为 linear,以便它与滚动同步平滑地进行。如果我们使用默认值 (ease),动画将从缓慢开始,然后迅速加速,最后在结束时减速——这不是我们从进度指示器中想要的!

css
.progress {
  animation-timeline: scroll(root);
  animation-name: scaleProgress;
  animation-duration: auto;
  animation-timing-function: linear;
}

我们可以使用 animation 简写属性将此内容压缩一些

css
.progress {
  animation: scaleProgress auto linear;
  animation-timeline: scroll(root);
}

注意:animation-timeline 目前不包含在简写中。但是,animation 属性会将 animation-timeline 重置为 auto(默认值),因此我们需要在 animation 简写之后声明 animation-timeline

多个动画

就像普通关键帧动画一样,我们可以同时应用多个滚动时间线动画,例如更改进度条的颜色。

css
.progress {
  animation:
    scaleProgress auto linear,
    colorChange auto linear;
  animation-timeline: scroll(root);
}

@keyframes scaleProgress {
  0% {
    transform: scaleX(0);
  }
  100% {
    transform: scaleX(1);
  }
}

@keyframes colorChange {
  0% {
    background-color: red;
  }
  50% {
    background-color: yellow;
  }
  100% {
    background-color: lime;
  }
}

使用不同的缓动函数

尽管我们故意在前面的示例中选择了线性缓动,但我们也可以使用 steps() 缓动来实现一些有趣的效果。此示例显示了一种不同类型的进度条,它使用离散步骤而不是平滑线性缩放。我们在进度条元素上设置了一个线性渐变背景,用于颜色段,然后动画化裁剪路径以依次显示每个段

css
.progress {
  background: linear-gradient(
    to right,
    red 20%,
    orange 0,
    orange 40%,
    yellow 0,
    yellow 60%,
    lime 0,
    lime 80%,
    green 0
  );
  animation: clip auto steps(5) forwards;
  animation-timeline: scroll(root);
}

@keyframes clip {
  0% {
    clip-path: polygon(0 0, 0 0, 0 100%, 0 100%);
  }
  100% {
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  }
}

重复和反转动画

滚动进度动画可以与现有的animation-directionanimation-iteration-count 属性结合使用。因此,我们可以让动画在滚动时间线中重复多次,或反向播放。在这里,当我们滚动时,“球”会弹跳多次。

css
.progress {
  animation: bounce auto linear 6 alternate;
  animation-timeline: scroll(root);
}

@keyframes bounce {
  100% {
    transform: translateY(-50vh);
  }
}

定位非祖先滚动容器

有时,我们可能希望动画化一个不是滚动容器后代的元素,但仍然将该元素的动画链接到滚动容器的进度。为此,我们需要创建一个命名滚动进度时间线。我们将通过使用简写scroll-timeline 属性(scroll-timeline-namescroll-timeline-axis 的简写)在滚动容器上声明时间线的名称和轴。同样,块轴是默认值。时间线名称必须以两个连字符作为前缀(类似于自定义属性),这将确保它不会与其他属性值冲突。

滚动容器必须是一个具有滚动能力的元素。

css
.scroller {
  max-height: 300px;
  overflow: scroll;
  scroll-timeline: --scale-progress block;
}

我们可以使用 animation-timeline 属性将要动画化的元素链接到滚动时间线。

css
/* Sibling of .scroller */
.progress {
  animation: scaleProgress auto linear;
  animation-timeline: --scale-progress;
}

@keyframes scaleProgress {
  0% {
    transform: scaleX(0);
  }
  100% {
    transform: scaleX(1);
  }
}

动画化滚动容器的祖先

如果要动画化的元素是滚动容器的兄弟元素,这将起作用。如果我们想要动画化祖先,或兄弟元素的后代,该怎么办?

我们需要另一个 CSS 属性,timeline-scope,它允许我们修改命名时间线的范围以包含设置它的元素。例如,如果我们在 body 上设置此属性,我们现在可以动画化该元素的背景颜色,即使它已经是滚动容器的祖先。

Image

让我们看一下代码

css
/* Ancestor element: We want to scope the scroll timeline to include this element and its descendants */
body {
  timeline-scope: --scale-progress;

  /* Apply the animation */
  animation: colorChange auto linear forwards;
  animation-timeline: --scale-progress;
}

/* The scroll container on which we declare our timeline */
.scroller {
  max-height: 300px;
  overflow: scroll;
  scroll-timeline: --scale-progress block;
}

/* Apply the animation on the sibling as before */
.progress {
  animation: scaleProgress auto linear;
  animation-timeline: --scale-progress;
}

注意:timeline-scope 目前仅在 Chrome Canary 和启用了实验性 Web 平台功能的 Chrome 116 中受支持。

探索创意示例

到目前为止,我们创建了一些相当基本的进度条动画——也许这是滚动进度时间线最明显的用例之一。但没有什么能阻止我们对滚动动画进行创意。

水平图像滚动器

当用户垂直滚动时水平动画化元素可以使网页感觉更动态,更少线性。在这里,我们正在动画化一排图像,以便它们在用户垂直滚动时从左侧滑入。

查看 CodePen 上的完整示例

使用运动路径

我们可以使用offset-path 在 CSS 中沿路径定位和动画化元素,以定义元素要遵循的运动路径。这是一种比矩形进度条更有趣的方式来指示进度!

查看 CodePen 上的完整示例

组合多个动画

在此演示中,我们正在滚动时动画化多个元素:文本被显示出来,而盒子从左到右滑动和翻滚。为了简化代码并避免创建多个关键帧,我们正在动画化一个自定义属性,并使用三角函数 计算 translateY 值,这些函数在所有主要浏览器的最新版本中都受支持。与转换属性不同,自定义属性在主线程上动画化,这意味着如果您想动画化大量的自定义属性,您的网站可能会出现性能下降。

查看 CodePen 上的完整示例

无障碍性和用户运动偏好

与任何侵入性动画一样,我们应该始终优先考虑无障碍性,并确保为那些不想使用动画的人关闭动画。对于滚动驱动动画来说,这一点尤其重要,因为即使是通常不会患有前庭疾病的用户,滚动驱动动画也会引起晕动症。如果您想了解更多信息,请查看尊重用户的运动偏好,以了解如何使用prefers-reduced-motion 媒体查询来确保您的动画无障碍。

总结

那么,CSS 中的滚动时间线动画与 JS 库相比如何(一旦它们得到普遍支持)?如果您正在创建特别复杂的动画,您可能仍然需要求助于像GSAP 这样的库,它尤其擅长处理复杂的编排。库也可能为我们提供自定义缓动等功能,而 GSAP 的 Inertia 插件(允许动画在滚动结束后平滑地停止,而不是突然停止)也是如此。目前,我们还没有办法在 CSS 中检测元素是否当前正在滚动。

同样,如果您的动画对用户体验至关重要,您可能需要暂时推迟,因为滚动关联动画可能要过一段时间才能得到普遍支持。

另一方面,如果您需要一些相对简单的滚动驱动动画,CSS 可以为您(和您的用户)节省大量的 JS 负载,从而为您带来巨大的性能提升!

希望您喜欢阅读这篇文章并探索示例。请随时在 DiscordGitHub 上留下您的反馈、想法或问题。

有用资源

关注 MDN

订阅 MDN 时事通讯,不错过任何关于最新 Web 开发趋势、技巧和最佳实践的更新。