css动画后台暂停是浏览器节电机制,非bug;requestanimationframe需手动结合document.hidden和visibilitychange控制启停,否则可能持续执行并拉高cpu。

CSS 动画在浏览器后台暂停不是 bug,是浏览器主动节电行为;requestAnimationFrame 虽也受节流影响,但必须手动控制启停,否则仍可能持续执行——这是两者最根本的差异。
为什么 CSS 动画在后台会“看起来”暂停
Chrome/Edge 会在标签页不可见时将 CSS 动画帧调度挂起,Firefox 更激进:直接冻结整个动画时间轴。此时 element.style.animationPlayState 读出来仍是 'running',但实际不渲染新帧。这不是失效,而是浏览器合成线程主动让渡资源。
- 动画属性如
transform、opacity被深度优化,挂起时几乎零 CPU 开销 - 例外情况:动态注入的
<style></style>+@keyframes,部分旧版 Android WebView 可能不识别隐藏状态,需手动加animation-play-state: paused -
visibility: hidden或display: none容器内的动画也会被停,和页面焦点无关
为什么 requestAnimationFrame 在后台仍可能跑满 CPU
requestAnimationFrame 是 JS 运行时机制,不自动感知 DOM 可见性。只要没调用 cancelAnimationFrame(),回调就照常注册——即使页面已切到后台、元素被 display: none,JS 主线程仍在执行。
- Chrome/Edge 会节流后台 tab 的
requestAnimationFrame到约 1fps,但 Safari 和多数安卓 WebView 完全不节流,仍按 60fps 执行 - 若回调里调用了
getBoundingClientRect()或offsetTop,每次都会强制触发同步布局计算,CPU 压力翻倍 - 典型错误写法:
function animate() { update(); requestAnimationFrame(animate); }—— 没做任何可见性判断
如何让 requestAnimationFrame 真正智能暂停
必须手动结合 document.hidden 和 visibilitychange 事件,不能依赖浏览器“可能节流”。核心逻辑是:隐藏时取消,显示时重启动画并重置时间戳。
- 监听
document.addEventListener('visibilitychange', () => { ... }),在document.hidden === true时调用cancelAnimationFrame(rafId) - 恢复时不要直接
requestAnimationFrame(animate),应先重置起始时间(如startTime = performance.now()),否则补帧会导致跳变 - 避免在
visibilitychange回调里高频反复启停,建议加状态锁或 100ms 防抖(尤其 iOS Safari 有延迟)
animation-play-state 不能替代 visibilitychange 控制
试图用 element.style.animationPlayState = 'paused' 来响应页面失焦,实际效果不可靠:
- 浏览器挂起 CSS 动画后,
animationPlayState属性值不变,设'paused'可能无效或延迟生效 - 若动画已因
animation-fill-mode: forwards停在终态,再设'running'不会重播,它已“完成”了 - iOS Safari 对 runtime 修改支持不稳定,尤其页面刚唤醒时,容易出现一帧不播或卡顿
真正关键的不是“怎么让它动”,而是“怎么让它动得省、动得准、动得可控”——performance.now() 校准时间、document.hidden 控制生命周期、try/catch 防止回调中断,这三者缺一不可。忽略任意一个,都可能在某个设备或浏览器上突然 CPU 拉满或动画消失。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











