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 中的滚动进度动画

阅读时间 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() 缓动来实现一些有趣的效果。这个例子展示了一种不同类型的进度条,它采用离散步长而不是平滑的线性缩放。我们为进度条元素设置了线性渐变背景作为颜色段,然后通过动画化 clip-path 来逐个显示它们。

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-timelinescroll-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 和 Chrome 116 中支持,且需要启用实验性 Web 平台功能。

探索创意示例

到目前为止,我们已经创建了一些相当基本的进度条动画——这可能是滚动进度时间线最明显的用例之一。但是,没有任何东西能阻止我们用滚动动画发挥创意。

水平图片滚动器

在用户垂直滚动时水平动画化元素可以使网页感觉更具动态性,不那么线性。这里,我们动画化一系列图片,让它们在用户垂直滚动时从左侧滑入。

在 CodePen 上查看完整示例。

使用运动路径

我们可以使用 offset-path 在 CSS 中定位和动画化元素沿路径移动,以定义元素要遵循的运动路径。这比矩形进度条有趣得多!

在 CodePen 上查看完整示例。

组合多个动画

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

在 CodePen 上查看完整示例。

可访问性和用户运动偏好设置

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

总结

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

同样,如果您的动画对用户体验至关重要,您可能需要暂时等待,因为滚动链接动画普遍得到支持还需要一段时间。

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

希望您喜欢阅读本文并探索示例。如果您有任何反馈、想法或问题,请随时在 DiscordGitHub 上留言。

有用资源