如何解决requestAnimationFrame失去页面焦点后帧率骤降问题?
解决requestAnimationFrame失焦后帧率下降的问题
问题根源
浏览器为了节省系统资源,在页面失焦(如窗口最小化、切换标签页)时会大幅降低requestAnimationFrame的调用频率,甚至直接暂停调用。再次聚焦页面时,elapsed会累积成一个远大于fpsInterval的数值,你的代码虽尝试调整then的值,但帧率计算依赖全局累积的frameCount和sinceStart,会因失焦期间的空白时间导致计算出的帧率骤降;同时过大的elapsed会让绘制逻辑在短时间内连续执行多次,表现为帧率突然下降的卡顿感。
解决方案及修改后的代码
通过三点优化即可解决该问题:
- 监听页面可见性变化,重新聚焦时重置时间基准
then,确保从当前时间开始计算帧间隔 - 限制
elapsed的最大值,避免单次循环处理过多累积时间,防止短时间内执行多次绘制逻辑 - 改用最近帧的时间差计算实时帧率,避免依赖全局累积值导致的误差
修改后的代码:
var stop = false; var fpsInterval, startTime, now, then, elapsed; // 用于计算实时帧率的临时变量 var lastFpsCalcTime = 0; var framesSinceLastCalc = 0; startAnimating(30); function startAnimating(fps) { fpsInterval = 1000 / fps; then = window.performance.now(); startTime = then; lastFpsCalcTime = then; console.log(startTime); animate(); // 监听页面可见性变化,聚焦时重置时间基准 document.addEventListener('visibilitychange', function() { if (!document.hidden) { then = window.performance.now(); lastFpsCalcTime = then; framesSinceLastCalc = 0; } }); } function animate(newtime) { if (stop) { return; } requestAnimationFrame(animate); now = newtime; elapsed = now - then; // 限制elapsed最大值,避免单次处理过多累积时间 if (elapsed > fpsInterval * 2) { elapsed = fpsInterval; then = now - elapsed; } if (elapsed > fpsInterval) { then = now - (elapsed % fpsInterval); // 此处编写你的绘制逻辑 // 优化帧率计算:每1秒统计一次最近的帧数 framesSinceLastCalc++; var timeSinceLastCalc = now - lastFpsCalcTime; if (timeSinceLastCalc >= 1000) { var currentFps = Math.round((framesSinceLastCalc / timeSinceLastCalc) * 1000 * 100) / 100; console.log(currentFps + " fps."); lastFpsCalcTime = now; framesSinceLastCalc = 0; } } }
关键优化点说明
- 页面可见性监听:页面从失焦状态恢复时,直接将
then重置为当前时间,避免用失焦期间累积的大时间差计算后续帧间隔 - elapsed上限限制:防止单次循环处理几十秒的累积时间,避免短时间内连续执行几十次绘制逻辑导致页面卡顿
- 实时帧率计算:不再依赖全局累积的帧计数和时间,而是每1秒统计一次最近的帧数,计算出的帧率更准确,也不会因失焦空白时间出现异常值
内容的提问来源于stack exchange,提问作者Sara Ree
相关产品推荐
相关产品推荐

