真正可扩展的做法是用 :nth-child(n) 配合 calc() 动态计算 animation-delay,如 li:nth-child(n) { animation-delay: calc((n - 1) * 0.15s); },需注意单位、兼容性及降级方案。

为什么直接写 animation-delay 不够用
手动给每个 li 写不同 animation-delay 值(比如 0.1s、0.2s)看似可行,但一旦增删列表项,就得同步改所有延迟值,维护成本高;更麻烦的是,动态渲染的列表(如 React/Vue 渲染)根本没法提前写死这些值。
真正可扩展的做法是让 CSS 自动计算延迟——核心就是利用 :nth-child(n) 的索引 n,配合 calc() 动态生成递增的 animation-delay。
关键前提:浏览器必须支持 calc() 在 animation-delay 中使用(Chrome 84+、Firefox 75+、Safari 15.4+ 均支持;旧 Safari 需要加 -webkit- 前缀)。
:nth-child(n) 如何配合 calc() 算出逐个延迟
CSS 里不能直接用变量或循环,但 :nth-child(n) 提供了天然的序号,再结合 calc() 就能构造出线性延迟。例如想让每项间隔 0.15s:
li:nth-child(n) {
animation-delay: calc((n - 1) * 0.15s);
}
注意几点:
-
n是从 1 开始计数,所以第一项延迟为calc((1 - 1) * 0.15s) = 0s - 必须写成
(n - 1),否则第一项就延迟0.15s,造成“首项等待”的意外空白 - 单位必须显式写出(如
0.15s),不能写0.15,否则calc()解析失败 - 如果用毫秒(如
150ms),确保所有单位一致,避免混用s和ms
滑入动画本身怎么写才自然
仅靠延迟不够,动画行为本身也影响观感。推荐用 transform: translateY() + opacity 组合,不触发重排,性能好:
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
<p>li {
animation: slideInUp 0.4s ease-out forwards;
animation-delay: calc((n - 1) * 0.15s);
}</p><p>li:nth-child(n) {
animation-delay: calc((n - 1) * 0.15s);
}</p>
几个实操细节:
- 务必加
forwards,否则动画结束瞬间会回退到初始状态(透明 + 下移) -
ease-out比linear更有“轻落”感;若想更柔和,可用cubic-bezier(0.22, 0.61, 0.36, 1) - 如果列表项高度不固定,避免用
height或max-height动画——它们会触发布局重排,卡顿明显 - 移动端要注意:部分安卓 WebView 对
calc()在animation-delay中的支持不稳定,建议加一层降级(见下一点)
兼容性兜底与常见失效原因
当 calc() 不被支持时,整个 animation-delay 声明会被忽略,结果是所有项同时滑入——这比错位或报错更隐蔽。可加一层简单降级:
li {
animation-delay: 0.15s; /* 降级:统一延迟,至少不是全崩 */
animation-delay: calc((n - 1) * 0.15s); /* 覆盖上一行 */
}
容易踩的坑:
-
:nth-child(n)匹配的是父元素下的第 n 个子元素,不是第 n 个li。如果列表里混有其他标签(如注释、空文本节点、div),序号会错乱。确保父容器只含li,或改用:nth-of-type() - Vue/React 中用
v-for/map()渲染时,若 key 不稳定,可能导致 DOM 节点复用,:nth-child序号和数据顺序不一致 - Safari 15.3 及更早版本不支持
calc()在animation-delay中使用,必须加-webkit-animation-delay并重复 calc 表达式
最常被忽略的一点:动画时间(0.4s)和间隔(0.15s)得匹配。如果列表有 20 项,总耗时接近 3s,用户可能等不及就划走了——别光顾着效果炫,得看实际场景的节奏感。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











