跟 Antfu 一起学习 CSS 渐入动画

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

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

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

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

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

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; 表示取消了之前的变换效果,元素恢复到它的原始形态和位置。

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

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

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

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 代码了:

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>

实现的效果如下:

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

css 复制代码
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 变量啦!

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

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

css 复制代码
[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 是一个媒体查询的功能,该功能用于检测用户是否有减少动画和动态效果的偏好。一些用户可能对屏幕上的快速或复杂动作敏感,这可能会导致不适或干扰体验,因此他们在操作系统中设置了减少动画的选项。

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

相关推荐
y先森4 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy4 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189114 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿6 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡7 小时前
commitlint校验git提交信息
前端
虾球xz7 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇7 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒7 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员7 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐7 小时前
前端图像处理(一)
前端