likes
comments
collection
share

跟 Antfu 一起学习 CSS 渐入动画

作者站长头像
站长
· 阅读数 2

周末无事,翻阅 Antfu 的博客,发现一篇很有意思的文章,用简单的 CSS animation 动画实现博客文章按照段落渐入,效果如下:

跟 Antfu 一起学习 CSS 渐入动画

是不是很有意思呢?作为一名前端开发,如果产品给你提出这样的动画需求,你能否实现出来呢?在继续阅读之前,不妨先独立思考一下,如何用 CSS 来完整这种动画。

PS:什么,你问 Antfu 是谁?他可是前端圈里面的偶像级人物:

Antfu 是 Anthony Fu 的昵称,他是一位知名的开源软件开发者,活跃于前端开发社区。Anthony Fu 以其对 Vue.js 生态系统的贡献而著名,包括但不限于 Vite、VueUse 等项目。Antfu 也因为他在 GitHub 上的活跃参与和贡献而受到许多开发者的尊敬和认可。

首先用 CSS 写一个渐入动画,相信这个大家都看得懂:

@keyframes enter {
  0% {
    opacity: 0;
    transform: translateY(10px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

上述代码定义了一个名为 enter 的关键帧动画,其效果使得元素从透明度为0(完全透明)逐渐变为透明度为1(完全不透明),同时元素会在垂直方向上从 10px 以上的位置移动到最终位置。具体来说,关键帧如下:

  • 0%:动画的起始状态(动画开始时刻)。在这个状态中,元素的透明度 opacity设置为0,表示元素是完全透明的,看不见的。同时,transform: translateY(10px); 属性表示元素在垂直方向上被推移了 10px,即元素的起始位置是它最终位置的上方 10px
  • to100%:动画的结束状态(动画结束时刻)。在这个状态中,元素的透明度 opacity设置为1,表示元素完全不透明,完全可见。transform: none; 表示取消了之前的变换效果,元素恢复到它的原始形态和位置。

难道这样就行了吗?当然不行,如果仅仅对内容添加上述动画,效果是文章整体渐入,效果如下:

跟 Antfu 一起学习 CSS 渐入动画

然而我们想要的效果是一段一段渐入呀,那怎么办呢?思路很简单:

给每个段落分别添加上述动画,然后按照先后顺序延迟播放动画。

[data-animate] {
  --stagger: 0;
  --delay: 120ms;
  --start: 0ms;
  animation: enter 0.6s both;
  animation-delay: calc(var(--stagger) * var(--delay) + var(--start));
}

上面的关键就是 animation-delay 这个属性,为了方便 HTML 编码,这里使用了 CSS 变量来进行控制,把元素的延迟时间总结到如下的公式里面:

calc(var(--stagger) * var(--delay) + var(--start));

其中变量的含义如下:

  • --stagger 是段落序号,值为1、2、3...
  • --delay 是上下两个段落的延迟时间间隔
  • --start 是初始延迟时间,即整片文章第一段的延迟偏移量

有了这些变量,就可以按照段落的前后顺序,写出如下 HTML 代码了:

<p style="--stagger: 1" data-animate>Block 1</p>
<p style="--stagger: 2" data-animate>Block 2</p>
<p style="--stagger: 3" data-animate>Block 3</p>
<p style="--stagger: 4" data-animate>Block 4</p>
<p style="--stagger: 5" data-animate>Block 5</p>
<p style="--stagger: 6" data-animate>Block 6</p>
<p style="--stagger: 7" data-animate>Block 7</p>
<p style="--stagger: 8" data-animate>Block 8</p>

实现的效果如下:

跟 Antfu 一起学习 CSS 渐入动画

可以说相当棒了!但是这里还有个问题,就是 markdown 文章转成 HTML 的时候,不会总是 p 标签吧,也有可能是 divpre 等其他标签,而且你还要手动给这些标签添加 --stagger 变量,这个简直不能忍啊。Antfu 最后给出的解决方案是这样的:

slide-enter-content > * {
  --stagger: 0;
  --delay: 150ms;
  --start: 0ms;
  animation: slide-enter 1s both 1;
  animation-delay: calc(var(--start) + var(--stagger) * var(--delay));
}

.slide-enter-content > *:nth-child(1) { --stagger: 1; }
.slide-enter-content > *:nth-child(2) { --stagger: 2; }
.slide-enter-content > *:nth-child(3) { --stagger: 3; }
.slide-enter-content > *:nth-child(4) { --stagger: 4; }
.slide-enter-content > *:nth-child(5) { --stagger: 5; }
.slide-enter-content > *:nth-child(6) { --stagger: 6; }
.slide-enter-content > *:nth-child(7) { --stagger: 7; }
.slide-enter-content > *:nth-child(8) { --stagger: 8; }
.slide-enter-content > *:nth-child(9) { --stagger: 9; }
.slide-enter-content > *:nth-child(10) { --stagger: 10; }
.slide-enter-content > *:nth-child(11) { --stagger: 11; }
.slide-enter-content > *:nth-child(12) { --stagger: 12; }
.slide-enter-content > *:nth-child(13) { --stagger: 13; }
.slide-enter-content > *:nth-child(14) { --stagger: 14; }
.slide-enter-content > *:nth-child(15) { --stagger: 15; }
.slide-enter-content > *:nth-child(16) { --stagger: 16; }
.slide-enter-content > *:nth-child(17) { --stagger: 17; }
.slide-enter-content > *:nth-child(18) { --stagger: 18; }
.slide-enter-content > *:nth-child(19) { --stagger: 19; }
.slide-enter-content > *:nth-child(20) { --stagger: 20; }

只要给文章容器增加 slide-enter-content 样式,那么通过 nth-child() 就能为其直接子元素按照顺序设置 stagger 变量啦!

跟 Antfu 一起学习 CSS 渐入动画

秒啊,实在是妙!不得不佩服大佬的脑洞,不过,杠精的你可能会说,我的文章又不止 20 个子元素,超过 20 怎么办呢?我说哥,你不会自己往后加嘛!

感兴趣的同学可以查看最终的样式代码,跟上述 demo 有一点点区别,相信你能从中学到不少东西,例如 Antfu 把 data-animate 属性关联的样式拆成了两段:

[data-animate] {
  --stagger: 0;
  --delay: 120ms;
  --start: 0ms;
}

@media (prefers-reduced-motion: no-preference) {
  [data-animate] {
    animation: enter 0.6s both;
    animation-delay: calc(var(--stagger) * var(--delay) + var(--start));
  }
}

写前端这么多年,我是第一次见到 @media (prefers-reduced-motion: no-preference) 这个媒体查询的用法,一脸懵逼,赶紧恶补了一把才知道:

在 CSS 中,@media 规则用于包含针对不同媒体类型或设备条件的样式。prefers-reduced-motion 是一个媒体查询的功能,该功能用于检测用户是否有减少动画和动态效果的偏好。一些用户可能对屏幕上的快速或复杂动作敏感,这可能会导致不适或干扰体验,因此他们在操作系统中设置了减少动画的选项。

因此,对于那些讨厌动画的用户,就不用展示这么花哨的效果,直接展示文章就行啦!