在CSS中使用滚动时间线的滚动链接动画的实际应用案例

780 阅读8分钟

Scroll-Linked Animations规范是对CSS的一个即将到来的实验性补充。使用该规范提供的@scroll-timeline at-rule和animation-timeline 属性,你可以通过滚动来控制常规CSS动画的时间位置。

在这篇文章中,我们将看看一些实际的使用案例,在这些案例中,滚动链接的动画会派上用场,取代了典型的JavaScript方法。

本篇文章中描述的CSS功能仍然是实验性的,没有最终确定。在写这篇文章的时候,除了Chromium≥89的浏览器启用了#experimental-web-platform-features 标志外,其他浏览器都不支持这些功能。

CSS滚动链接动画,一个快速入门指南

通过CSS滚动链接动画,你可以通过滚动来驱动一个CSS动画:当你在一个滚动容器中向上或向下滚动时,链接的CSS动画就会前进或后退。最重要的是,这一切都在主线程之外,在合成器上运行。

要实现一个基本的滚动链接动画,你需要三个东西。

  1. 一个CSS动画
  2. 一个滚动时间线
  3. 两者之间的一个链接

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由描述符组成,包括。

  1. source 描述了可滚动的元素,该元素的滚动会触发激活并驱动时间轴的进展。默认情况下,这就是整个文档。
  2. orientation 决定了应该触发动画的滚动方向。默认情况下,这是vertical
  3. 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实现的解决方案。

  1. 视差标题
  2. 图像显示/隐藏
  3. 打字动画
  4. 旋转木马指示器
  5. 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的每个元素上附加两个动画。

  1. 第一个动画是当适当的部分出现在文档的底部边缘时,在视觉上激活ToC项目。
  2. 第二个动画是当适当的部分在文档的顶部边缘滑出视野时,在视觉上停用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的浏览器中被支持,隐藏在一个标志后面。