浏览器如何暂停/更改JavaScript时选项卡或窗口未激活?

IT技术 javascript browser
2021-01-29 13:38:36

背景:我正在做一些用户界面测试,需要检测人们是否在关注。但是,这个问题是不是有关的页面知名度API

具体来说,我想知道如果当前选项卡在不同浏览器中未处于活动状态或浏览器窗口未处于活动状态,我的 Javascript 代码将如何受到影响。到目前为止,我已经挖掘了以下内容:

我有以下问题:

  • 除了移动浏览器,桌面浏览器是否会在选项卡不活动时暂停 JS 执行?何时使用哪些浏览器?
  • 哪些浏览器减少了setInterval重复?它只是减少到一个限制还是一个百分比?例如,如果我有 10 毫秒的重复和 5000 毫秒的重复,那么每个重复会受到什么影响?
  • 如果窗口失焦,而不仅仅是选项卡,是否会发生这些变化(我想它会更难检测,因为它需要 OS API。)
  • 在活动选项卡中是否有任何其他效果不会被观察到?他们会不会把本来可以正确执行的事情搞砸(即前面提到的 Jasmine 测试)?
3个回答

测试一

我为此专门编写了一个测试:
帧速率分布:setInterval vs requestAnimationFrame

注意:此测试非常占用 CPU。requestAnimationFrameIE 9- 和 Opera 12- 不支持。

该测试记录 asetIntervalrequestAnimationFrame在不同浏览器中运行所需的实际时间,并以分布的形式为您提供结果。您可以更改毫秒数setInterval以查看它在不同设置下的运行情况。对于延迟setTimeout,其工作方式与 a 类似setIntervalrequestAnimationFrame通常默认为 60fps,具体取决于浏览器。要查看切换到其他选项卡或使用非活动窗口时会发生什么,只需打开页面,切换到其他选项卡并等待一段时间。它将继续在非活动选项卡中记录这些功能所需的实际时间。

测试二

另一种测试它的方法是使用setInterval重复记录时间戳requestAnimationFrame并在分离的控制台中查看它。当您使选项卡或窗口处于非活动状态时,您可以看到它的更新频率(或是否曾经更新过)。

结果

Chrome
ChromesetInterval将选项卡处于非活动状态时限制为大约1000ms的最小间隔如果间隔大于 1000ms,它将以指定的间隔运行。窗口是否失焦并不重要,只有当您切换到不同的选项卡时,间隔才会受到限制。requestAnimationFrame选项卡处于非活动状态时暂停。

// Provides control over the minimum timer interval for background tabs.
const double kBackgroundTabTimerInterval = 1.0;

https://codereview.chromium.org/6546021/patch/1001/2001

Firefox
类似于Chrome,FirefoxsetInterval将标签(不是窗口)处于非活动状态时将最小间隔限制为大约1000ms。However, requestAnimationFrameruns exponentially slower when the tab is inactive, with each frame taking 1s, 2s, 4s, 8s and so on.

// The default shortest interval/timeout we permit
#define DEFAULT_MIN_TIMEOUT_VALUE 4 // 4ms
#define DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE 1000 // 1000ms

https://hg.mozilla.org/releases/mozilla-release/file/0bf1cadfb004/dom/base/nsGlobalWindow.cpp#l296

Internet Explorer
IE 不会限制setInterval选项卡处于非活动状态时的延迟,但会requestAnimationFrame在非活动选项卡中暂停窗口是否失焦并不重要。

Edge
从 Edge 14 开始,setInterval在非活动选项卡中的上限为 1000 毫秒。requestAnimationFrame在非活动选项卡中始终暂停。

Safari
就像Chrome,setInterval当标签处于非活动状态时,Safari Caps在100毫秒时。requestAnimationFrame也暂停了。

Opera
自采用 Webkit 引擎以来,Opera 表现出与 Chrome 相同的行为。setInterval上限为 1000 毫秒,并requestAnimationFrame在选项卡处于非活动状态时暂停。

概括

非活动选项卡的重复间隔:

           setInterval      requestAnimationFrame 
Chrome
9- 不受影响 不支持
10 不受影响 暂停
11+ >=1000 毫秒暂停

火狐
3- 不受影响 不支持
4 不受影响 1s
5+ >=1000ms 2 n s(n = 自不活动以来的帧数)

IE
9- 不受影响 不支持
10+ 不受影响 暂停

边缘
13-不受影响暂停
14+ >=1000 毫秒暂停

苹果浏览器
5- 不受影响 不支持
6 不受影响 暂停
7+ >=1000 毫秒暂停

歌剧
12- 不受影响 不支持
15+ >=1000 毫秒暂停
当浏览器最小化时,可以使用它每 5 秒重新加载一次页面吗?,是我的问题。
2021-03-16 13:38:36
通过about:config在浏览器中打开 url并将dom.min_background_timeout_value更改为1000 以外值,显然可以更改 Firefox setInterval 最小值
2021-04-01 13:38:36
@AndrewMao 我不知道。我在开发一个库以可靠地检测 JS 是否使用setInterval重新启用时遇到了这个问题requestAnimationFrame我所知道的是,它的setTimeout行为与 类似setInterval,因为它们在 Firefox 和 Chrome 中具有相同的最小背景间隔,而在其他浏览器中没有明显限制。
2021-04-09 13:38:36
很好的答案。除了setIntervaland之外的函数还有其他可能的已知差异requestAnimationFrame吗?
2021-04-10 13:38:36
请注意,requestAnimationFrame如果用户只是切换应用程序(Alt+Tab 退出 Chrome),则 chrome 不会暂停/降低调用速率只要该选项卡在 Chrome 中处于活动状态,“帧速率”或多或少是恒定的。
2021-04-11 13:38:36

我观察到:在Chrome 中的非活动选项卡上,所有等待时间小于1000 毫秒的setTimeout(必须相同setInterval四舍五入为1000 毫秒我认为不会修改更长的超时时间。

似乎是自Chrome 11Firefox 5.0以来的行为https : //developer.mozilla.org/en-US/docs/DOM/window.setTimeout#Inactive_tabs

此外,我不认为当整个窗口处于非活动状态时它的行为方式(但似乎很容易调查)。

实际上它与jQueryJavascript没有任何关系,因为它是内部浏览器实现。
2021-03-15 13:38:36
jQueryfocusblur事件似乎同时检测选项卡和窗口开关,因此可以想象它可以双向工作。但我想知道窗口如何检测它是否真的可见。
2021-03-17 13:38:36
你能在 2016 年底确认这一点吗?
2021-03-20 13:38:36

一个更新的答案来补充这些:在 chrome 78.0.3904.108 上,当我移动到不同的选项卡时,我注意到所有这些超时(不仅仅是那些低于 1000 毫秒的超时)花费的时间比预期的要长一些,然后又回来了。我看到的行为更准确地描述为“非活动选项卡上的所有超时可能会延迟一些额外的时间,最多为 1000 毫秒。” 尝试运行以下命令并切换到另一个选项卡!

let timeouts = [ 500, 1000, 2000, 3000, 10000 ];

let minExcess = document.getElementsByClassName('minExcess')[0];

timeouts.forEach(ms => {
  let elem = document.getElementsByClassName(`t${ms}`)[0];
  let cnt = 0;
  
  let lastMs = +new Date();
  let f = () => {
    let curMs = +new Date();
    let disp = document.createElement('p');
    let net = curMs - lastMs;
    lastMs = curMs;
        
    setTimeout(f, ms);
    if (minExcess.value && (net - ms) < parseInt(minExcess.value)) return;
    
    disp.innerText = `${net},`;
    elem.appendChild(disp);
    if (++cnt > 10) elem.firstElementChild.remove();
    
  };
  setTimeout(f, ms);
  
});
body { font-size: 80%; }
div {
  max-height: 80px;
  overflow-x: auto;
  background-color: rgba(0, 0, 0, 0.1);
  margin-bottom: 2px;
  white-space: nowrap;
}
p { margin: 0; }
div > p {
  margin: 0;
  display: inline-block;
  vertical-align: top;
  margin-right: 2px;
}
input { margin: 0 0 10px 0; }
.t500:before { display: block; content: '500ms'; font-weight: bold; }
.t1000:before { display: block; content: '1000ms'; font-weight: bold; }
.t2000:before { display: block; content: '2000ms'; font-weight: bold; }
.t3000:before { display: block; content: '3000ms'; font-weight: bold; }
.t10000:before { display: block; content: '10000ms'; font-weight: bold; }
<p>Ignore any values delayed by less than this amount:</p>
<input type="text" class="minExcess" value="200" pattern="^[0-9]*$"/>
<div class="timeout t500"></div>
<div class="timeout t1000"></div>
<div class="timeout t2000"></div>
<div class="timeout t3000"></div>
<div class="timeout t10000"></div>