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

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

相关推荐
我爱学习_zwj9 分钟前
深入浅出Node.js-1(node.js入门)
前端·webpack·node.js
IT 前端 张36 分钟前
2025 最新前端高频率面试题--Vue篇
前端·javascript·vue.js
喵喵酱仔__37 分钟前
vue3探索——使用ref与$parent实现父子组件间通信
前端·javascript·vue.js
_NIXIAKF38 分钟前
vue中 输入框输入回车后触发搜索(搜索按钮触发页面刷新问题)
前端·javascript·vue.js
InnovatorX39 分钟前
Vue 3 详解
前端·javascript·vue.js
布兰妮甜39 分钟前
html + css 顶部滚动通知栏示例
前端·css·html
种麦南山下41 分钟前
vue el table 不出滚动条样式显示 is_scrolling-none,如何修改?
前端·javascript·vue.js
杨荧2 小时前
【开源免费】基于Vue和SpringBoot的贸易行业crm系统(附论文)
前端·javascript·jvm·vue.js·spring boot·spring cloud·开源
疯狂小料3 小时前
HTML5语义化编程
前端·html·html5
萌萌哒草头将军3 小时前
🚀🚀🚀快来靓仔,给你看个大宝贝,我不允许你还不知道这个提效工具
前端·vue.js·react.js