animation-delay通过错开各元素动画启动时机实现依次播放,如0s、0.2s、0.4s;需避免display/visibility干扰,推荐CSS变量或:nth-child配合JS动态设置,优先使用transform/opacity保障性能。

用 animation-delay 控制多元素依次播放的原理
多个元素使用同一套 @keyframes 动画时,靠给每个元素设置不同的 animation-delay 值,就能实现“一个接一个”播放的效果。关键不是动画本身有多复杂,而是每个元素的启动时机错开了——比如第一个元素 animation-delay: 0s,第二个 animation-delay: 0.2s,第三个 animation-delay: 0.4s,以此类推。
注意:这个 delay 是相对于元素进入渲染流程(或满足触发条件)的时间点开始计算的。如果元素是静态 HTML,那基本就是页面加载完成、样式计算完毕后立即生效;如果是动态插入的节点,delay 从它被 append 到 DOM 后才开始计时。
实际写法:循环生成 delay 值最稳妥
手动给每个元素写不同 animation-delay 容易出错,尤其元素数量多或后续要增删时。推荐用 CSS 自定义属性 + :nth-child() 或 JS 动态注入。
- 纯 CSS 方案(适合固定结构):
div.item { animation: fade-in 0.3s ease-out; } div.item:nth-child(1) { animation-delay: 0s; } div.item:nth-child(2) { animation-delay: 0.15s; } div.item:nth-child(3) { animation-delay: 0.3s; } div.item:nth-child(4) { animation-delay: 0.45s; } - 更灵活的 CSS 变量方案(需配合 JS 初始化一次):
div.item { --delay: 0s; animation: slide-up 0.4s cubic-bezier(0.2, 0.8, 0.4, 1) var(--delay); }然后用 JS 给每个div.item设置style.setProperty('--delay', `${i * 0.15}s`) - 避免直接用
transition-delay替代——它只对属性变化生效,不适用于完整动画周期控制
常见翻车点:动画重播、父容器 visibility / display 干扰
即使设置了 animation-delay,也可能出现“全卡住不动”或“突然一起播”,多数是因为父级容器用了 visibility: hidden 或 display: none 切换状态。
立即学习“前端免费学习笔记(深入)”;
-
display: none会让子元素完全脱离渲染流,animation-delay会重置,等display: block后才重新计时——但此时所有元素几乎同时触发 -
visibility: hidden虽保留布局,但部分浏览器(尤其是旧版 Safari)会暂停其内部动画计时器 - 解决办法:用
opacity: 0; pointer-events: none;配合transform: translateY(20px)来“隐藏”,确保动画能正常预热和延迟启动 - 如果需要反复播放,记得加
animation-fill-mode: backwards,否则 delay 期间元素会显示初始态而非动画第一帧
性能提醒:大量元素 + 复杂动画容易掉帧
每多一个元素,就多一个独立的动画计时器在运行。当列表超过 20+ 项、且动画含 box-shadow、filter 或频繁重排属性(如 height、width)时,主线程压力明显上升。
- 优先用
transform和opacity——它们走合成层,不触发布局重算 - 避免在
@keyframes中写left/top,改用transform: translateX() - 移动端慎用
animation-timing-function: steps()配合大量元素,某些 Android Webview 渲染不稳定 - 真要处理长列表?考虑只对视口内元素启用动画,其余用
animation: none先关掉
delay 的数值精度其实没那么重要,0.05s 和 0.07s 在人眼看来没区别,但设成 0.1s 整数倍更容易调试和维护。真正难的是让动画节奏和交互反馈对得上——比如按钮点击后,列表入场 delay 得跟点击反馈的微交互动画衔接上,这点常被忽略。









