先记住这个答案
animation-play-state: paused 让动画停在当前帧,时间轴不再走,改成 running 后从暂停的位置继续播放。负的 animation-delay,比如 -2s,让动画跳过开头的 2 秒,直接从 2 秒处开始播放,跳过的部分不会再补回来。两者的功能方向不一样:paused 是暂停和恢复,负 delay 是快进到某个时间点。
paused冻结当前帧,恢复后接着播- 负
delay跳过开头,从中间开始 paused可切换,负延迟只作用一次- 两者可以同时使用互不干扰
暂停和恢复:animation-play-state
animation-play-state 有两个值:running 和 paused。默认是 running,动画正常播放。设成 paused 后动画立刻冻结,元素保持冻结帧的样式不再变化。
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.icon {
animation: spin 3s linear infinite;
}
.icon:hover {
animation-play-state: paused;
}鼠标移到图标上动画停住,移走后继续从停住的角度转。不是重头开始,而是接着转。这个特性在做交互式动画时非常有用,比如用户悬停时暂停轮播、离开后继续。
用 JavaScript 控制也一样:el.style.animationPlayState = 'paused' 暂停,el.style.animationPlayState = 'running' 恢复。
快进到中间帧:负 animation-delay
正的 animation-delay 让动画延迟开始。负值是反过来,让动画「假装已经播了一段」,直接从中间开始。比如一个 4 秒的动画设了 animation-delay: -2s,页面一加载元素就处于 2 秒那个位置的样式,然后从那里往后播。
@keyframes wave {
0% { transform: translateY(0); }
50% { transform: translateY(-20px); }
100% { transform: translateY(0); }
}
.dot { animation: wave 1.5s ease-in-out infinite; }
.dot:nth-child(1) { animation-delay: 0s; }
.dot:nth-child(2) { animation-delay: -0.3s; }
.dot:nth-child(3) { animation-delay: -0.6s; }三个圆点用同一个动画,但通过负 delay 错开起始位置,页面打开时三个点就已经处于波浪的不同阶段,形成连续的波浪效果。如果用正的 delay,第二个和第三个圆点会先不动,等延迟结束后才开始跳,看起来不协调。
两者叠加使用的效果
animation-play-state 和 animation-delay 互不干扰,可以同时使用。比如设了 animation-delay: -2s 和 animation-play-state: paused,动画会跳到 2 秒的位置然后立刻冻结在那一帧。
这个组合技在实际项目里有个经典用法:用负 delay 指定初始位置,用 paused 冻结,然后用 CSS 自定义属性或 JavaScript 动态控制进度。比如一个进度条动画,总时长 1 秒,通过改变 animation-delay 的负值来控制进度条停在哪个位置。
通过 JavaScript 设置 el.style.animationDelay = '-0.6s',进度条就停在 60% 的位置。改成 -0.8s 就跳到 80%。整个过程不需要手动计算样式值,浏览器根据关键帧自动插值。
容易答错的地方
- 以为负
delay和paused效果类似 - 两者的方向完全不同。
paused是暂停当前帧不走了,负delay是跳过开头直接快进。暂停可以恢复,快进不能倒回去。记住一个是遥控器上的暂停键,一个是快进键。 - 以为暂停后再恢复动画会从头开始
- 恢复后动画从暂停的位置继续,不是从头开始。如果想让动画重头播放,需要先移除
animation属性,触发一次重排(比如读一下offsetHeight),再重新加上。
面试官还会怎么问?
暂停期间 animationend 会触发吗?
不会。暂停期间时间轴不走,动画不会到达终点,所以 animationend 不会触发。恢复后动画继续走完剩余部分,走完才触发。
负 delay 超过动画总时长会怎样?
如果动画 2 秒但 delay 是 -5s,超出的部分会按循环次数处理。单次动画会直接到达终点状态。无限循环动画会算出 5 秒对应哪一帧,从那里开始。
怎么用 CSS 做一个拖拽控制进度的效果?
可以把 animation-play-state 设成 paused,然后用 JavaScript 在拖拽事件里动态修改 animation-delay 的负值。用户拖到哪里就把 delay 设成对应的负时长,元素就停在那一帧。松手后把 play-state 改成 running,动画就从那个位置继续播放。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。