仅用 CSS 实现滚动驱动动画指南
自苹果在 2007 年首次将 CSS 动画引入 Web 以来,CSS 动画已经取得了长足的进步。最初只是简单的效果,例如从一种颜色动画到另一种颜色,如今已演变为精美复杂的图像在页面上扭曲和飞舞。
但传统上,将这些动画与滚动等用户行为关联起来需要第三方库和相当多的 JavaScript 代码,这会增加代码的复杂性。然而现在,我们只需几行 CSS 即可实现滚动驱动的动画。
滚动驱动动画的浏览器支持度已提高,并在 Safari 26 beta 中可用,使您更容易在页面上创建引人注目的效果。让我向您展示如何操作。
首先,让我们分解滚动驱动动画的组成部分。
滚动驱动动画包含三个部分

- 目标:页面上我们要进行动画处理的元素
- 关键帧:当用户滚动时元素会发生什么
- 时间轴:决定动画是否进行的因素
这三个部分的出色之处在于,其中两个您可能已经很熟悉了。
第一个是目标,它可以是您想在页面上移动的任何东西,并可以随心所欲地设置样式。
第二个是关键帧,这是已经存在多年的经典 CSS 动画。创建出色的滚动驱动动画很大程度上取决于您的动画有多棒。如果您不熟悉 CSS 动画,请查阅 MDN 的资源。
第三部分是时间轴,它可能不太熟悉,但却是滚动驱动动画的重要组成部分。让我们更详细地探讨它。
什么是时间轴?
动画有开始、中间和结束,沿着时间轴按顺序移动。Web 上的默认时间轴称为文档时间轴,它是基于时间的。这意味着随着时间的推移,时间轴也会前进。
如果我有一个使用默认时间轴的动画,它会随着时间的推移而动画。如果我从一个绿色圆圈开始,并想使用默认时间轴来动画颜色变化,那么我可能会让它在第一秒变成红色,然后第二秒变成蓝色,第三秒变成黄色。颜色随着时间而动画。
多年来动画都是这样工作的。然后,animation-timeline
属性作为 CSS 动画级别 2 规范的一部分于 2023 年 6 月引入。这使我们能够考虑除了时间流逝之外可能影响动画的其他因素,例如用户在网页上上下滚动,从而使滚动驱动动画成为可能。
使用滚动驱动动画,我们不再使用时间。取而代之的是,我们有两种新的时间轴类型可以使用:滚动时间轴和视图时间轴。
scroll()
时间轴
使用滚动时间轴,动画不随时间推进,而是根据用户的滚动来推进。
如果用户开始滚动,动画就开始了。滚动一停止,动画也停止。正是这个新的时间轴创建了滚动和动画之间的链接。
演示滚动驱动动画工作原理的一个常见方法是创建一个进度条。实际上,由于您已经有滚动条,您不需要这样的进度条,但这是一个易于理解的示例,所以我们姑且用它。
首先要做的是创建我的目标,即我们要进行动画处理的元素。
让我们将这个目标作为我为编码学校 A-School of Code 网站的一部分来构建。如果我们要在我页面底部创建一个进度条,我们可以将其添加为页脚的一个伪元素。我们希望它从左下角开始,然后向右前进。
代码可能看起来像这样
footer::after {
content: "";
height: 1em;
width: 100%;
background: rgba(254, 178, 16, 1);
left: 0;
bottom: 0;
position: fixed;
}
这将使我们得到一个穿过我页面的窄黄色条(由粉色箭头突出显示)

接下来,我们需要关键帧来创建实际的动画。
我们将给我们的关键帧一个自定义名称,比如“progress-expand”,像这样
@keyframes progress-expand {
from { width: 0% }
to { width: 100% }
}
第三,我们需要使用我们的新时间轴 — **** scroll()
。这会告诉我的浏览器,动画只应在用户滚动时生效,使其成为一个滚动驱动动画。
footer::after {
...
animation-timeline: scroll();
}
最后,我们需要通过将 animation
属性添加到我们的进度条来将这三个组件联系起来,像这样
footer::after {
...
animation: progress-expand;
animation-timeline: scroll();
}
注意:您的 animation-timeline
属性必须在 animation
属性之后设置,否则将不起作用。
就这样,您就拥有了第一个滚动驱动动画。
因为我们向页面添加了运动效果,所以在发布之前我们需要考虑一件事。我们刚刚添加的运动是否会给用户带来任何运动不适?我们的页面是否可访问?
进度条的细微、通常缓慢的移动不太可能引发运动敏感性,部分原因是它没有占据观看者太多的视野。相比之下,更大、更广视野的动画通常模拟三维空间中的运动,使用视差、缩放或其他景深技术(如焦点模糊)。对运动敏感的用户更有可能将这些大型动画体验为三维空间中的真实运动,因此更有可能出现其他负面症状,如不适或眩晕。
当有疑问时,最好将动画包装在一个媒体查询中,检查用户是否有减少运动的偏好设置,像这样
@media not (prefers-reduced-motion) {
/* animation here */
}
这样,您的动画只有在用户未设置减少运动偏好时才会运行。要了解何时使用 prefers-reduce-motion 的更多信息,请阅读我们的文章《运动响应式设计》(https://webkit.ac.cn/blog/7551/responsive-design-for-motion/)。在这种情况下,我认为我们的动画是安全的。
这是我们的最终结果
所有代码集中在此处,方便查看
footer::after {
content: "";
height: 1em;
width: 100%;
background: rgba(254, 178, 16, 1);
inset-inline-start: 0;
bottom: 0;
position: fixed;
transform-origin: top left;
animation: grow-progress linear;
animation-timeline: scroll();
}
@keyframes grow-progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
View() 时间轴
我们在上面的示例中使用的 scroll()
时间轴,在我们开始滚动时立即激活,而不管用户可见的内容是什么,或者它何时出现在视口中。
这可能正是您想要的,但通常情况下,您希望动画在目标元素出现在页面上时发生。
您的元素可以是任何东西——轮播、菜单、图片画廊。但无论是什么,在大多数网站上,您想要动画的元素通常不会永久显示在页面上。相反,它会在用户浏览您的网站时出现,当您向下滚动足够远时,它会进入视口。
因此,您不希望时间轴在用户开始滚动时激活,而是希望它在元素出现在视口中时激活,为此,我们需要为动画使用不同的时间轴——view()
时间轴。
为了了解它是如何工作的,让我们看一个简单的例子:当图片在我们滚动时进入我的视口时,它会滑动到位。
我将从一篇使用占位符文本的基本文章开始,并在页面的不同部分插入几张图片。由于图片在文章的更下方,所以页面首次加载时它们不在视口中。
让我们回顾一下滚动驱动动画所需的三个要素
- 目标:页面上我们要进行动画处理的元素
- 关键帧:当用户滚动时元素会发生什么
- 时间轴:决定动画是否进行的因素
我的目标是文章中的图片,它们在我的 HTML 中。很好!完成一个,还有两个。
接下来,我需要设置我的关键帧。这是滚动驱动动画的动画部分。
我希望这里发生两件事——我希望图片渐入,并从右侧滑入。我可以用以下代码实现这一点
@keyframes slideIn {
0% {
transform: translateX(100%);
opacity: 0;
}
100% {
transform: translateX(0%);
opacity: 1;
}
}
最后,我需要在我的图片标签中设置新的 animation-timeline
,以便它在视口中时激活。
img {
animation-timeline: view();
}
现在我有了这三个部分,我需要通过在图片上设置 animation
属性将它们组合起来。
请记住,首先设置 animation
属性很重要。否则,这将不起作用。
img {
animation: slideIn;
animation-timeline: view();
}
这样我们就得到了我们想要的漂亮的滑动效果。
但对于这个动画,我还有一件事想做。您会注意到,图片直到快要离开视口时才完成滑动到位。这意味着我们的图片在可见的整个过程中都在运动,这对于我们的用户来说体验并不好。
我们真正希望的是它滑动到位后能停留一段时间,以便用户在不受所有运动干扰的情况下正确地欣赏它。我们可以使用另一个名为 animation-range
的属性来实现这一点。
animation-range
告诉浏览器何时沿时间轴开始和停止动画。默认范围是 0% 到 100%。0% 代表目标元素开始进入视口的那一刻。100% 代表目标元素完全离开视口的那一刻。
因为我们没有设置 animation-range
,所以我们使用的是默认值。这意味着只要我的图片第一个像素进入视口,我的动画就开始了,直到最后一个像素退出才结束。
为了让我的用户更容易实际看到这些图片,我希望动画在它们进入视口大约一半时停止。在那一点上,我希望图片找到它的位置并停留在那里。为此,我将把范围更改为 0% 和 50%,像这样
img {
animation: slideIn;
animation-timeline: view();
animation-range: 0% 50%;
}
这次,动画在我想要的位置,即图片到达页面中间时停止了。
您感觉到变化带来的不同了吗?它是否让查看图片变得更容易?您认为不同的范围会更好吗?问自己这些问题能帮助我们更好地理解这些变化对用户意味着什么,所以花点时间思考一下是件好事。
在发布之前,还需要考虑一点是此动画对运动敏感用户的影响。就像我们之前的示例一样,由于我们引入了运动,我们必须检查是否会引发可能的运动不适。
在这种情况下,我的动画比进度条大。我页面上的图片很大,如果您不期望它们移动并且滚动速度太快,它们可能会快速划过。这可能会引起不适。由于我想稳妥起见,我将把它放在一个减少运动的媒体查询中,所以我的最终代码将看起来像这样
img {
@media not (prefers-reduced-motion) {
animation: slideIn;
animation-timeline: view();
animation-range: 0% 50%;
}
}
现在,我可以发布了。
下一步
您还可以利用动画时间轴和动画范围做更多事情。由于 scroll()
和 view()
是函数,您可以传入特定值来真正自定义您的滚动驱动动画的工作方式。您可以更改默认的滚动元素。这是设置时间轴的带有滚动条的元素。如果您不传入任何值,默认值为 nearest
,它将查找最近的带有滚动条的祖先元素。但您也可以将其设置为 root
和 self
。您可以更改的第二个值是滚动条轴。默认值为 block
,但您也可以将其设置为 inline
、x
和 y
。亲自尝试不同的值,看看它们是如何工作的。
您还可以围绕动画元素的进入和退出做更多事情,以获得您想要的确切效果。我们将在未来的文章中介绍这些。在此期间,请在 Safari 26 beta 中尝试使用滚动和视图时间轴,看看如何提升您网站或 Web 应用程序的用户交互。
如果您尝试了,请告诉我们您对滚动驱动动画的看法。通过 BlueSky 给我的 Saron Yitbarek 发消息,或者联系我们的其他宣传者——Jen Simmons,通过 Bluesky / Mastodon,以及 Jon Davis,通过 Bluesky / Mastodon。您还可以在 LinkedIn 上关注 WebKit。如果您发现错误或问题,请提交 WebKit 错误报告。