CSS 中的滚动进度动画

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

当我们想到滚动驱动的动画时,我们通常意味着以下两件事之一:

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

滚动驱动动画规范涵盖了这两种类型的动画。在本文中,我们将首先介绍滚动进度时间轴,顾名思义,它将动画与滚动进度联系起来。

使用动画时间轴

在这个例子中,我们将实现一个常见的功能:为一个简单的进度条添加动画效果,使其随着用户滚动网页从左向右缩放。由于我们希望将动画与根滚动条的进度关联起来,因此可以使用匿名滚动进度时间轴。

首先我们来定义动画本身。我们希望进度条从左向右缩放,因此我们将使用transform

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

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

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

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

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

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

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

我们可以使用简写属性来稍微压缩一下animation

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

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

多个动画

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

.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 动画依次显示每个颜色段:

.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-direction滚动进度动画可以与现有的和属性结合使用animation-iteration-count。因此,我们可以让动画在滚动时间轴上重复播放多次,或者反向播放。这里,“球”在滚动时弹跳了几次。

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

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

定位非祖先滚动容器

有时,我们可能希望为非滚动容器后代元素添加动画,但仍将该元素的动画与滚动容器的进度关联起来。为此,我们需要创建一个命名的滚动进度时间轴。我们将使用简写属性( andscroll-timeline的简写)在滚动容器上声明时间轴的名称和轴。同样,块轴是默认设置。时间轴名称必须以两个短划线作为前缀(类似于自定义属性),以确保它不会与其他属性值冲突。scroll-timeline-name``scroll-timeline-axis

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

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

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

/* 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例如,如果我们在 上设置此属性,那么即使该元素是滚动容器的祖先元素,我们现在也可以为其背景颜色设置动画。

图像

我们来看看代码:

/* 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。

探索创意示例

到目前为止,我们已经创建了一些相当基本的进度条动画——这或许是滚动进度时间轴较为常见的用例之一。但我们仍然可以发挥创意,打造更具创意的滚动动画。

水平图像滚动条

在用户垂直滚动时水平移动元素,可以让网页更具动感,减少线性感。这里我们为一排图片添加动画效果,让它们在用户垂直滚动时从左侧滑入。

查看 CodePen 上的完整示例

使用运动路径

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

查看 CodePen 上的完整示例

组合多个动画

在这个演示中,我们为多个元素的滚动添加动画:文本显示,同时框从左向右滑动并翻转。为了简化代码并避免创建多个关键帧,我们为一个自定义属性添加动画,并translateY使用三角函数计算其值,所有主流浏览器的最新版本都支持该函数。与变换属性不同,自定义属性在主线程上进行动画处理,这意味着如果您尝试为大量自定义属性添加动画,您的网站性能可能会受到影响。

查看 CodePen 上的完整示例

可访问性和用户运动偏好

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

概括

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

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

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