先记住这个答案
CSS 动画的 animation-delay 只在第一轮播放之前生效,之后的循环紧接着上一轮结束就开始。CSS 没有提供「每轮之间加间隔」的属性。通行的做法是把 animation-duration 设成实际动画时长加上想要的停顿时长,然后在 @keyframes 里把动作集中写在前面一部分百分比内,后面的百分比保持不变。这样后半段元素不动,看起来就像在等待。
animation-delay只作用于第一轮- 拉长总时长、动作集中在前半段
- 剩余百分比保持不动当作停顿
- 也可以用
animationiteration手动控制
用关键帧百分比制造停顿
假设你想做一个心跳效果:放大再缩回,然后停 1 秒,再重复。实际动画动作需要 1 秒,停顿 1 秒,所以总时长设成 2 秒。动作占前 50% 的时间,后 50% 保持不动。
@keyframes heartbeat {
0% { transform: scale(1); }
25% { transform: scale(1.3); }
50% { transform: scale(1); }
100% { transform: scale(1); } /* 50%~100% 保持不动,充当停顿 */
}
.heart {
animation: heartbeat 2s ease-in-out infinite;
}50% 到 100% 这段时间元素保持 scale(1) 不动,视觉上就是每跳一次停 1 秒。想让停顿更长,把 animation-duration 拉大,同时把动作部分的百分比压缩。比如总时长 4 秒、动作占 25%,停顿就变成了 3 秒。
调整停顿时长的计算方式
假设动作本身需要 T 秒,想在两轮之间停顿 P 秒。总时长设成 T + P 秒,动作占比是 T / (T + P),换算成百分比写到 @keyframes 里。
比如动作 0.5 秒、停顿 2 秒:总时长 2.5 秒,动作占 20%。@keyframes 里 0% 到 20% 写动作,20% 到 100% 保持结束状态。
@keyframes blink {
0% { opacity: 1; }
10% { opacity: 0; }
20% { opacity: 1; }
100% { opacity: 1; }
}
.cursor {
animation: blink 2.5s linear infinite;
}要注意 animation-timing-function 作用于整个周期而不只是动作段。如果用了 ease-in-out,停顿前后会有加速减速的感觉。想让动作段有缓动、停顿段完全静止,可以在关键帧里单独写 animation-timing-function。
用 JavaScript 在每轮之间加真正的延迟
如果不想用百分比技巧,也可以用 JavaScript 监听 animationiteration 事件,在每轮结束时暂停动画,过一段时间再恢复。
animationiteration 手动加延迟JavaScriptconst el = document.querySelector('.box');
el.addEventListener('animationiteration', () => {
el.style.animationPlayState = 'paused';
setTimeout(() => {
el.style.animationPlayState = 'running';
}, 1000); // 停顿 1 秒
});这种方式可以精确控制停顿时长,也不用重新算百分比。缺点是依赖 JavaScript,纯 CSS 方案做不到这一点。
容易答错的地方
- 以为
animation-delay每轮都会生效 animation-delay只作用于动画的第一次启动。设了infinite之后,从第二轮开始就没有延迟了。很多人以为每轮之间都会等一下,实际上后续的循环是无缝衔接的。- 把停顿段的关键帧值写错
- 停顿段的样式必须和动作段的结束样式一致。比如动作段最后是
scale(1),停顿段从头到尾也要是scale(1)。如果写成了别的值,「停顿」期间元素会慢慢过渡到那个值,就不是真正的停顿了。
面试官还会怎么问?
能不能用两个动画叠加来实现停顿?
可以,但比较绕。一个动画负责实际效果,另一个动画用 steps() 在特定时间段把元素藏起来。实际项目里用关键帧百分比更直观,代码也更好维护。
停顿期间想让元素完全消失再出来,怎么做?
把停顿段的样式改成 opacity: 0 或 visibility: hidden,动作段开头再恢复。不过要注意 visibility 不支持平滑过渡,opacity 可以,看你要不要有淡入淡出的效果。
为什么不用 animation-delay 配合 animationend 重新播放?
这样做可以,但需要在 animationend 回调里先移除动画、用 requestAnimationFrame 或 offsetHeight 触发重排、再加回去,代码比较啰嗦。而且 infinite 动画不会触发 animationend,这个方案就不适用了。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。