现代浏览器失焦时会主动限制css动画以节电,chrome/edge降低raf频率至约1s一次,firefox直接暂停动画;应使用pagevisibility api监听并用performance.now()时间戳校准动画进度。

页面失焦后CSS动画突然卡住或跳帧,是浏览器主动限制的
现代浏览器(Chrome、Edge、Firefox)在标签页失去焦点时,会主动降低 requestAnimationFrame 的触发频率,甚至暂停 CSS 动画和过渡——这不是 bug,是节电与性能策略。动画不会报错,但你会看到 animation-play-state 仍是 running,实际却不动了。
- Chrome/Edge 默认将后台页的定时器节流到约 1s 一次,
@keyframes帧更新被挂起 - Firefox 更激进:失焦后直接暂停所有 CSS 动画,重获焦点才恢复(除非显式设置)
-
visibility: hidden或display: none容器内的动画也会被停,和窗口焦点无关
想让动画在后台继续跑?基本做不到,但可以绕过视觉断层
你无法强制浏览器“不挂起”CSS动画,但能避免用户切回来时出现明显跳变。关键是别依赖连续帧,改用状态驱动 + 时间戳校准:
- 用
performance.now()记录上一帧时间,切回时计算已流逝时间,手动跳转到对应动画进度 - 对轮播、倒计时等场景,改用
setTimeout+transform,而非纯animation - 避免用
animation-iteration-count: infinite配合长周期动画(如 30s),失焦再切回大概率跳过中间几十次循环
示例:用 JS 控制旋转进度比纯 CSS 更可控:
let start = performance.now();<br>function animate() {<br> const elapsed = (performance.now() - start) % 3000;<br> element.style.transform = `rotate(${elapsed / 3000 * 360}deg)`;<br> requestAnimationFrame(animate);<br>}
pageVisibility API 是唯一可靠监听失焦/重获焦点的方式
document.hidden 和 visibilitychange 事件比 blur/focus 更准确,它反映的是页面是否真正对用户可见(包括最小化、切标签、锁屏等):
立即学习“前端免费学习笔记(深入)”;
- 监听
document.addEventListener('visibilitychange', ...),在document.hidden === true时暂停逻辑、保存状态 - 不要依赖
window.onblur,它不触发于多数标签页切换场景 - 注意 Safari 在 iOS 上可能延迟触发
visibilitychange,建议加 100ms 防抖
用 animation-play-state: paused 手动控制反而更不可靠
有人试图用 JS 监听 visibilitychange 后手动设 element.style.animationPlayState = 'paused',这看似合理,但实际有坑:
- 浏览器挂起动画时,
animation-play-state属性值不变,仍为running,你设paused可能无效或延迟生效 - 切回时设回
running,动画会从当前时间点硬启,若后台挂起太久,会出现明显跳帧 - 多个嵌套动画、
animation-delay不同的元素,手动同步状态极难维护
真要保精度,就别碰 CSS 动画的“自动播放”机制,把时间轴完全交给 JS 管理。
浏览器对后台动画的处理不是统一标准,而是各厂按功耗模型动态调整的;你写的 @keyframes 很可能在某个版本 Chrome 里被静默降频,在另一个版本里被直接冻结——别猜,用 visibilitychange 感知,用时间戳补偿,这才是稳的。










