先记住这个答案
暂停期间修改同名 @keyframes,动画不会因此从头开始,暂停的时间进度也保持不变。但它并未锁定画面:样式更新后,浏览器可用新关键帧在当前进度重新计算属性值,元素可能在仍暂停时就跳到新位置。恢复播放只让时间继续前进,不是第一次读取新关键帧的时机。
- 暂停记住的是进度,不是属性值
- 修改同名关键帧不重启动画
- 改
animation-name会重新开始 - 暂停时重算关键帧也可能跳帧
暂停冻结的是进度而非样式
一个常见误解是“暂停等于把当前样式固定住了”。实际上 animation-play-state: paused 冻结的是动画时间,画面由这个时间点和当前关键帧共同决定。更改关键帧规则会引起样式重新计算,因此同一个 50% 进度也可以映射到新的位置。恢复播放时,时间继续从暂停位置向前走。
@keyframes slide {
from { transform: translateX(0); }
to { transform: translateX(300px); }
}
.box {
animation: slide 3s linear forwards;
}
/* 通过 JS 添加这个类来暂停 */
.box.paused {
animation-play-state: paused;
}
/* 暂停后用 CSSOM 把 300px 改成 100px,
样式重算时 50% 进度可从 150px 跳到 50px */假设动画播到 50% 时暂停,旧关键帧对应 translateX(150px)。此时用 CSSOM 把终点从 300px 改为 100px,新关键帧在同样进度对应 translateX(50px)。样式重新计算后就可能看到跳变,无须等恢复;恢复后它从 50% 时间进度继续向 100px 运动。可在暂停状态读 getComputedStyle 验证当前位置。
修改关键帧的方式通常是用 document.styleSheets API 找到对应的 CSSKeyframesRule,再操作它的 cssRules。也可以删掉旧的 <style> 标签再插入新的,效果一样。
改 animation-name 和改 @keyframes 内容的区别
如果把 animation-name 换成另一个名字,旧动画会被移除,新动画通常从自己的起点开始。animation-play-state: paused 若仍写在元素上,新动画也会保持暂停,而不是自动开始播放。修改同名关键帧规则则不应被当作重启动画的方法。
这个区别在进度条与过场动画中很实用。进度条希望保留当前时间进度,应维持动画身份,只调整同名关键帧或改用 Web Animations API 精确设置进度。过场动画想从头展示,可明确移除后重新添加动画,并同时检查 animation-play-state 的值,别只靠替换关键帧文本猜结果。
容易答错的地方
- 以为暂停会锁住当前画面
- 暂停冻结时间进度,不冻结关键帧计算结果。同名关键帧变化并触发样式重算后,当前进度对应的属性值可能变化。若看不到变化,先确认修改的是正在使用的关键帧规则,或检查新旧关键帧在当前进度是否恰好给出相同值。
- 以为改了
animation-name动画会接着播 - 换名字通常创建新动画,旧进度不沿用;但元素若仍设置
animation-play-state: paused,新动画也会暂停。想从头播放,还要把播放状态切回running。
面试官还会怎么问?
暂停和 animation-delay 设成负值有什么关系?
没有直接关系。animation-delay 为负值会让动画跳过开头一段,但动画一旦开始播放,暂停和恢复跟 delay 就无关了。暂停冻结的是已经经过的时间,delay 只影响动画什么时候开始。
怎么在暂停时获取动画当前的进度?
可以用 getComputedStyle 读取当前动画的属性值,或者用 Web Animations API 的 element.getAnimations() 拿到 Animation 对象,通过它的 currentTime 属性获得精确的毫秒级进度。
prefers-reduced-motion 媒体查询下应该怎么处理暂停?
如果用户开启了减少动画的系统设置,通常应该用 animation: none 彻底移除动画,而不是暂停在某一帧。暂停留下的画面可能是一个不合适的中间状态,直接去掉动画让元素回到没有动画时的默认样式更安全。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。