当浏览器页面不可见状态时,你的定时器还准时吗?

背景

在使用 setTimeout 时碰到的一个问题,浏览器最小化或者不可见时,定时器表现的行为不一致。本文记录了测试出来的实践结果,供大家参考。

前言

setInterval 和 setTimeout,作为我们日常开发经常使用到的定时器方法,大家一定非常熟悉。

javascript 复制代码
    setInterval(function () {
        //todo something
        console.log('123')
    }, 500);

相信大家都知道这段代码执行的结果,每个1s在控制台,打印出一个'123'。当然在实际运行还需要考虑js的宏任务和微任务的执行时间。定时器中的方法触发可能需要在宏任务队列中排队,不一定会在刚好到间隔时间执行。关于Event Loop机制我们就不在这里讨论了。有兴趣的同学可以查看这篇文章 阮一峰老师的JavaScript 运行机制详解:再谈Event Loop

但是如果我们把浏览器从当前页面切换到另一个标签页或者把浏览器最小化了,定时器还是会按照我们的预想执行吗?接下来我们一探究竟。

我们将测试setInterval、setTimeout、requestAnimationFrame这三个方法在浏览器可见以及不可见状态下的表现。

页面可见和不可见状态

参考阮一峰老师的另一篇文章 Page Visibility API

这个API在document 对象上,新增了一个document.visibilityState属性。该属性返回一个字符串,表示页面当前的可见性状态,共有三个可能的值。

  • hidden:页面彻底不可见。
  • visible:页面至少一部分可见。
  • prerender:页面即将或正在渲染,处于不可见状态。

visibilityState属性发生变化,就会触发visibilitychange事件。因此,可以通过监听这个事件(通过document.addEventListener()方法或document.onvisibilitychange属性),跟踪页面可见性的变化。

javascript 复制代码
    // 浏览器可见状态切换事件
    document.addEventListener("visibilitychange", function () {
      if (document.visibilityState === "hidden") {
        console.log("页面不可见");
      } else {
        console.log("页面可见");
      }
    });

setInterval

代码:
javascript 复制代码
// js
let timer = 0;
document.getElementById("btn").addEventListener("click", function () {
  timer = setInterval(function () {
    const myDate = new Date();
    const currentDate = `${date.getMinutes()}m ${date.getSeconds()}s ${date.getMilliseconds()}ms`;
    // 打印currentDate
    console.log("setInterval:" + currentDate);
  }, 500);
});

document.getElementById("btn1").addEventListener("click", function () {
  clearInterval(timer);
});
结果
  1. 时间间隔设置小于1s,图例是以500ms为例。
chrome firefox safari
< 1s

经过多次验证我们发现,定时器的间隔在小于1s时,当页面不可见之后时间间隔变成了1s。safari前后表现不一致,不可见状态变成了没有规律的间隔。

  1. 接下来我们把时间间隔调整为大于1s,图例以2s为例。
chrome firefox safari
>= 1s

经过多次验证,chrome和firefox前后表现一致,时间间隔不变。safari前后表现不一致,由1s变成了2s,3s等等没有规律的间隔。

setTimeout

代码
javascript 复制代码
document.getElementById("btnTimeout").addEventListener("click", function () {
    timeOutTimer();
});

document.getElementById("btnCleatTimeout").addEventListener("click", function () {
    clearTimeout(timer);
});

function timeOutTimer() {
  timer = setTimeout(function () {
    const date = new Date();
    const currentDate = `${date.getMinutes()}m ${date.getSeconds()}s ${date.getMilliseconds()}ms`;
    console.log("setTimeout:" + currentDate);
    timeOutTimer();
  }, 2000);
}
  1. 时间间隔设置小于1s,图例是以300ms为例。
chrome firefox safari
< 1s

我们发现setTimeoutsetInterval类似,定时器的间隔在小于1s时,当页面不可见之后时间间隔变成了1s。safri前后表现不一致,不可见状态变成了没有规律的间隔。

  1. 接下来我们将时间间隔改成2s,再来验证。
chrome firefox safari
>= 1s

同样setTimeoutsetInterval类似,定时器的间隔在不小于1s时,当页面不可见时前后表现一致,时间间隔不变。safri前后表现不一致,不可见状态变成了没有规律的间隔。

结论

我们可以得出结论,在大多数浏览器中setTimeoutsetInterval 未被激活的tabs的定时最小延迟>=1000ms,浏览器为了优化后台tab的加载损耗(以及降低耗电量),在未被激活的tab中定时器的最小延时限制为1S(1000ms)。

requestAnimationFrame

代码
javascript 复制代码
function rafTimer() {
  const date = new Date();
  const currentDate = `${date.getMinutes()}m ${date.getSeconds()}s ${date.getMilliseconds()}ms`;
  console.log("requestAnimationFrame:" + currentDate);
  timer = window.requestAnimationFrame(rafTimer);
}

document.getElementById("btnRAF").addEventListener("click", function () {
  rafTimer();
});

document
  .getElementById("btnCancelRAF")
  .addEventListener("click", function () {
    window.cancelAnimationFrame(timer);
  });
结果
chrome firefox safari

我们可以发现,在chrome和safari中,当浏览器状态为不可见时,raf方法将停止执行。这是符合预期的,但是在firefox中浏览器当状态变为不可见时,会在间隔是1s,2s,4s,8s,16s,32s...这样的顺序下去执行raf方法。

在MDN中明确写道:

为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的iframe 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。

写在最后

对于大多数浏览器,在页面未激活状态都会对定时器做一些优化处理。setTimeoutsetInterval在间隔小于1s时会变成>=1s的间隔;requestAnimationFrame,在大多数浏览器中会被暂停调用。

实验代码

相关推荐
前端没钱8 分钟前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
汪洪墩13 分钟前
【Mars3d】设置backgroundImage、map.scene.skyBox、backgroundImage来回切换
开发语言·javascript·python·ecmascript·webgl·cesium
NoneCoder13 分钟前
CSS系列(29)-- Scroll Snap详解
前端·css
无言非影17 分钟前
vtie项目中使用到了TailwindCSS,如何打包成一个单独的CSS文件(优化、压缩)
前端·css
我曾经是个程序员43 分钟前
鸿蒙学习记录
开发语言·前端·javascript
羊小猪~~1 小时前
前端入门之VUE--ajax、vuex、router,最后的前端总结
前端·javascript·css·vue.js·vscode·ajax·html5
摸鱼了1 小时前
🚀 从零开始搭建 Vue 3+Vite+TypeScript+Pinia+Vue Router+SCSS+StyleLint+CommitLint+...项目
前端·vue.js
程序员shen1616111 小时前
抖音短视频saas矩阵源码系统开发所需掌握的技术
java·前端·数据库·python·算法
Ling_suu2 小时前
SpringBoot3——Web开发
java·服务器·前端
Yvemil72 小时前
《开启微服务之旅:Spring Boot Web开发》(二)
前端·spring boot·微服务