Scroll-Linked Animations规范是对CSS的一个即将到来的实验性补充。使用该规范提供的@scroll-timeline at-rule和animation-timeline 属性,你可以通过滚动来控制常规CSS动画的时间位置。
在这篇文章中,我们将看看一些实际的使用案例,在这些案例中,滚动链接的动画会派上用场,取代了典型的JavaScript方法。
本篇文章中描述的CSS功能仍然是实验性的,没有最终确定。在写这篇文章的时候,除了Chromium≥89的浏览器启用了#experimental-web-platform-features 标志外,其他浏览器都不支持这些功能。
CSS滚动链接动画,一个快速入门指南
通过CSS滚动链接动画,你可以通过滚动来驱动一个CSS动画:当你在一个滚动容器中向上或向下滚动时,链接的CSS动画就会前进或后退。最重要的是,这一切都在主线程之外,在合成器上运行。
要实现一个基本的滚动链接动画,你需要三个东西。
- 一个CSS动画
- 一个滚动时间线
- 两者之间的一个链接
CSS动画
这是一个我们已经知道的普通的CSS动画。
@keyframes adjust-progressbar {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
就像你通常做的那样,用 animation 属性把它附加到一个元素上。
#progressbar {
animation: 1s linear forwards adjust-progressbar;
}
滚动时间线
滚动时间线允许我们将滚动距离映射到动画进度上。在CSS中,我们用CSS@scroll-timeline at-rule来描述这一点。
@scroll-timeline scroll-in-document-timeline {
source: auto;
orientation: vertical;
scroll-offsets: 0%, 100%;
}
这个at-rule由描述符组成,包括。
source描述了可滚动的元素,该元素的滚动会触发激活并驱动时间轴的进展。默认情况下,这就是整个文档。orientation决定了应该触发动画的滚动方向。默认情况下,这是vertical。scroll-offsets属性是一个关键点的数组,描述动画应该被激活的范围。它可以是绝对值(例如百分比和长度)或基于元素。
以前的规范版本要求你也要设置一个time-range 描述符。这个描述符已被删除,并将自动接管链接动画的animation-duration 。你可能仍然在演示中看到它的痕迹,但你可以安全地忽略它。
两者之间的联系
为了将我们的@scroll-timeline 与我们的CSS动画联系起来,我们使用新的animation-timeline CSS属性,并让它指代时间线的名称。
#progressbar {
animation: 1s linear forwards adjust-progressbar;
animation-timeline: scroll-in-document-timeline; /* 👈 THIS! */
}
有了这个设置,adjust-progressbar 动画就不会在页面加载时自动运行,而是在我们向下滚动页面时才会前进。
关于@scroll-timeline 的更深入介绍,请参考我关于滚动链接动画的未来系列的第一部分和第二部分。
第一篇文章更详细地看了每个描述符/属性,用一个例子来解释它们,然后涵盖了许多更有趣的演示。
第二篇文章更深入地探讨了基于元素的偏移,它允许我们在滚动时驱动一个元素出现在滚动端口并消失的动画。
一个使用基于元素的偏移来实现CSS滚动链接动画的例子。
实际使用案例
除了上面的进度演示外,还有一些用例或场景,滚动链接动画可以取代通常使用JavaScript实现的解决方案。
- 视差标题
- 图像显示/隐藏
- 打字动画
- 旋转木马指示器
- scrollspy
视差式页眉
Scroll-Linked Animations的一个典型用例是视差效果,即一个页面的几个部分似乎有不同的滚动速度。有一种方法可以只用CSS来创建这种类型的效果,但这需要令人费解的transform ,包括translate-z() 和scale()。
在Firewatch Header的启发下,我创建了这个使用CSS滚动时间线的版本--它使用了上述transform 黑客。
CodePen Embed Fallback
与原来的演示相比。
- 除了不再需要的那个额外的
.parallax__cover,标记被保留了。 <body>被赋予一个min-height,以创造一些滚动状态。.parallax元素和它的.parallax_layer子元素的定位被调整了。transform/perspective-hack被替换成一个滚动时间线。
每个不同的层都使用相同的滚动时间线:滚动的距离为100vh 。
@scroll-timeline scroll-for-100vh {
time-range: 1s;
scroll-offsets: 0, 100vh;
}
.parallax__layer {
animation: 1s parallax linear;
animation-timeline: scroll-for-100vh;
}
各层之间的不同之处在于我们向下滚动时它们移动的距离。
- 最前面的层应该留在原地,例如,移动为
0vh。 - 最后一层应该是移动最快的,例如:
100vh。 - 中间的所有层都是插值的。
@keyframes parallax {
to {
transform: translateY(var(--offset));
}
}
.parallax__layer__0 {
--offset: 100vh;
}
.parallax__layer__1 {
--offset: 83vh;
}
.parallax__layer__2 {
--offset: 67vh;
}
.parallax__layer__3 {
--offset: 50vh;
}
.parallax__layer__4 {
--offset: 34vh;
}
.parallax__layer__5 {
--offset: 17vh;
}
.parallax__layer__6 {
--offset: 0vh;
}
当最前面的层在更大的距离上移动时,它们看起来比下面的层移动得更快,从而达到视差效果。
图像显示/隐藏
滚动链接动画的另一个很好的用途是图像显示.
默认情况下,图像的不透明度为0,并且使用了一个 [clip-path](https://css-tricks.com/clipping-masking-css/#the-new-clip-path):
#revealing-image {
opacity: 0;
clip-path: inset(45% 20% 45% 20%);
}
在最终状态下,我们希望图像是完全可见的,所以我们发送我们的动画的结束帧来反映这一点。
@keyframes reveal {
to {
clip-path: inset(0% 0% 0% 0%);
opacity: 1;
}
}
通过使用基于元素的偏移量作为我们的滚动时间线偏移量,我们可以让它在图像本身滑入视图时才开始出现。
@scroll-timeline revealing-image-timeline {
scroll-offsets:
selector(#revealing-image) end 0.5,
selector(#revealing-image) end 1
;
}
#revealing-image {
animation: reveal 1s linear forwards;
animation-timeline: revealing-image-timeline;
}
😵不能跟随这些基于元素的偏移?这个可视化/工具已经帮你解决了。
打字动画
由于CSS滚动时间线可以与任何现有的CSS动画相联系,你基本上可以采取任何CSS动画演示并对其进行改造。以打字动画为例。
通过添加滚动时间线和animation-timeline 属性,它可以被调整为 "滚动时打字"。
CodePen Embed Fallback
注意,为了创造一些滚动状态,<body>也被赋予了一个高度,即300vh 。
使用一个不同的动画,上面的代码可以很容易地被调整,以创建一个滚动的缩放效果。
我可以看到这两个对文章介绍来说非常有用。
旋转木马/滑块指标
旋转木马(又称滑块)的组件之一是一个指示器,显示它包含多少张幻灯片,以及哪张幻灯片是当前活动的。这通常是使用子弹来完成的。
正如Fabrizio Calderan创建的这个演示中所展示的那样,我们将能够使用CSS滚动时间线来实现这一点。
活动状态的子弹是通过.slider nav::before ,并有一个动画设置,使其在其他子弹上移动。
/* Styling of the dots */
.slider nav::before, .slider a {
inline-size: 1rem;
aspect-ratio: 1;
border-radius: 50%;
background: #9bc;
}
/* Positioning of the active dot */
.slider nav::before {
content: "";
position: absolute;
z-index: 1;
display: block;
cursor: not-allowed;
transform: translateX(0);
animation: dot 1s steps(1, end) 0s forwards;
}
/* Position over time of the active dot */
@keyframes dot {
0%
{ transform: translateX(0); }
33%
{ transform: translateX(calc((100% + var(--gap)) * 1)); }
66%
{ transform: translateX(calc((100% + var(--gap)) * 2)); }
100%
{ transform: translateX(calc((100% + var(--gap)) * 3)); }
}
通过在滑块上附加一个@scroll-timeline ,表示活动状态的点可以在你滚动时移动。
@scroll-timeline slide {
source: selector(#s);
orientation: inline;
}
.slider nav::before {
/* etc. */
animation-timeline: slide;
}
由于在动画中加入了 steps() 的功能,这个点只有在滑块扣到它的位置后才会移动。当移除它时,点是如何随着你的滚动而移动的就变得更清楚了
💡 这感觉像是Christian Shaefer的纯CSS旋转木马的最后一块缺失。
ScrollSpy
早在2020年初,我创建了一个具有滚动活动状态的粘性目录。创建演示的最后一部分是使用IntersectionObserver ,在你向上/向下滚动文档时,设置目录(ToC)中的活动状态。
与上面的旋转木马指标演示不同,我们不能简单地通过移动一个点来达到目的,因为要调整的是目录中的文本。为了处理这种情况,我们需要在ToC的每个元素上附加两个动画。
- 第一个动画是当适当的部分出现在文档的底部边缘时,在视觉上激活ToC项目。
- 第二个动画是当适当的部分在文档的顶部边缘滑出视野时,在视觉上停用ToC项目。
.section-nav li > a {
animation:
1s activate-on-enter linear forwards,
1s deactivate-on-leave linear forwards;
}
由于我们有两个动画,我们也需要创建两个滚动时间线,而且是针对每一部分的内容。以#introduction 部分为例。
@scroll-timeline section-introduction-enter {
source: auto;
scroll-offsets:
selector(#introduction) end 0,
selector(#introduction) end 1;
}
@scroll-timeline section-introduction-leave {
source: auto;
scroll-offsets:
selector(#introduction) start 1,
selector(#introduction) start 0;
}
一旦这两个时间线都与两个动画相连接,一切都会如期进行。
.section-nav li > a[href"#introduction"] {
animation-timeline:
section-introduction-enter,
section-introduction-leave;
}
最后
我希望我已经让你相信了CSS滚动链接动画规范所提供的潜力。不幸的是,它现在只在基于Chromium的浏览器中被支持,隐藏在一个标志后面。