当 Chrome 中的选项卡处于非活动状态时,如何使 setInterval 也能工作?

IT技术 javascript google-chrome setinterval
2021-01-29 01:44:04

setInterval每秒运行一段代码 30 次。这很好用,但是当我选择另一个选项卡时(以便带有我的代码的选项卡变为非活动状态),setInterval出于某种原因设置为空闲状态。

我做了这个简化的测试用例(http://jsfiddle.net/7f6DX/3/):

var $div = $('div');
var a = 0;

setInterval(function() {
    a++;
    $div.css("left", a)
}, 1000 / 30);

如果您运行此代码然后切换到另一个选项卡,等待几秒钟然后返回,动画会在您切换到另一个选项卡时继续。因此,如果选项卡处于非活动状态,动画不会每秒运行 30 次。这可以通过计算setInterval每秒调用函数的次数来确认- 如果选项卡处于非活动状态,这将不是 30,而是 1 或 2。

我猜这是为了提高性能而设计的,但是有什么方法可以禁用这种行为?在我的情况下,这实际上是一个缺点。

6个回答

在大多数浏览器上,非活动选项卡的执行优先级较低,这会影响 JavaScript 计时器。

如果您的过渡值是使用帧之间经过的实时时间而不是每个间隔的固定增量来计算的,您不仅可以解决这个问题,还可以通过使用requestAnimationFrame来实现更流畅的动画,因为如果处理器不是,它可以达到 60fps很忙。

这是一个使用动画属性转换的普通 JavaScript 示例requestAnimationFrame

var target = document.querySelector('div#target')
var startedAt, duration = 3000
var domain = [-100, window.innerWidth]
var range = domain[1] - domain[0]

function start() {
  startedAt = Date.now()
  updateTarget(0)
  requestAnimationFrame(update)
}

function update() {
  let elapsedTime = Date.now() - startedAt

  // playback is a value between 0 and 1
  // being 0 the start of the animation and 1 its end
  let playback = elapsedTime / duration

  updateTarget(playback)
  
  if (playback > 0 && playback < 1) {
  	// Queue the next frame
  	requestAnimationFrame(update)
  } else {
  	// Wait for a while and restart the animation
  	setTimeout(start, duration/10)
  }
}

function updateTarget(playback) {
  // Uncomment the line below to reverse the animation
  // playback = 1 - playback

  // Update the target properties based on the playback position
  let position = domain[0] + (playback * range)
  target.style.left = position + 'px'
  target.style.top = position + 'px'
  target.style.transform = 'scale(' + playback * 3 + ')'
}

start()
body {
  overflow: hidden;
}

div {
    position: absolute;
    white-space: nowrap;
}
<div id="target">...HERE WE GO</div>


对于后台任务(非 UI 相关)

@UpTheCreek 评论:

对于演示问题很好,但仍有一些事情需要您继续运行。

如果您有需要在给定时间间隔内精确执行的后台任务,您可以使用HTML5 Web Workers看看下面 Möhre 的回答以了解更多细节......

CSS vs JS“动画”

这个问题和许多其他问题可以通过使用 CSS 转换/动画而不是基于 JavaScript 的动画来避免,这会增加相当大的开销。我会推荐这个jQuery 插件,它可以让你像animate()方法一样从 CSS 转换中受益

@nthpixel 会在 tat 情况下添加 .stop (根据 www 的回答)帮助(因为每次都会清除以前的动画)
2021-03-21 01:44:04
@gordatron - .stop 对我的情况没有帮助。同样的行为。我现在使用的是 FireFox 6.0.2。我想知道这是否是 jQuery 实现 .animate 的方式。
2021-03-25 01:44:04
我在 FireFox 5 上试过这个,即使选项卡不在焦点上,“setInterva”仍然运行。我在“setInterval”中的代码使用“animate()”滑动幻灯片。看起来动画是由 FireFox 排队的。当我返回选项卡时,FireFox 会立即一个接一个地弹出队列,导致快速移动的幻灯片,直到队列为空。
2021-03-26 01:44:04
我会更改要读取的代码的最后一行,before = now;以获取自上次调用开始而不是结束以来经过的时间这通常是您在制作动画时想要的。As it is now, if an execution of the interval function takes 15ms, elapsedTimewill give 35ms (instead of 50ms which is the interval) next time it is called (when the tab is active).
2021-04-03 01:44:04
@cvsguimaraes - 是的,很好。您是否知道规范中的任何内容可以保证即使在后台选项卡中也能运行 Web Worker?我有点担心浏览器供应商将来也会随意决定限制这些......
2021-04-05 01:44:04

我在音频淡入淡出和 HTML5 播放器方面遇到了同样的问题。当标签变为非活动时,它被卡住了。所以我发现 WebWorker 可以不受限制地使用间隔/超时。我用它来向主要的 javascript 发布“滴答声”。

网络工作者代码:

var fading = false;
var interval;
self.addEventListener('message', function(e){
    switch (e.data) {
        case 'start':
            if (!fading){
                fading = true;
                interval = setInterval(function(){
                    self.postMessage('tick');
                }, 50);
            }
            break;
        case 'stop':
            clearInterval(interval);
            fading = false;
            break;
    };
}, false);

主要的Javascript:

var player = new Audio();
player.fader = new Worker('js/fader.js');
player.faderPosition = 0.0;
player.faderTargetVolume = 1.0;
player.faderCallback = function(){};
player.fadeTo = function(volume, func){
    console.log('fadeTo called');
    if (func) this.faderCallback = func;
    this.faderTargetVolume = volume;
    this.fader.postMessage('start');
}
player.fader.addEventListener('message', function(e){
    console.log('fader tick');
    if (player.faderTargetVolume > player.volume){
        player.faderPosition -= 0.02;
    } else {
        player.faderPosition += 0.02;
    }
    var newVolume = Math.pow(player.faderPosition - 1, 2);
    if (newVolume > 0.999){
        player.volume = newVolume = 1.0;
        player.fader.postMessage('stop');
        player.faderCallback();
    } else if (newVolume < 0.001) {
        player.volume = newVolume = 0.0;
        player.fader.postMessage('stop');
        player.faderCallback();
    } else {
        player.volume = newVolume;
    }
});
我用这个做了一个很棒的节拍器。有趣的是,所有最流行的 html5 鼓机都不使用这种方法,而是使用劣质的常规 setTimeout。skyl.github.io/grimoire/fretboard-prototype.html - chrome 或 safari 现在效果最好。
2021-03-12 01:44:04
如果开发人员开始滥用它,他们将“修复”它。除了极少数例外,您的应用程序并不重要,它应该在后台消耗资源以提供一些最小的用户体验改进。
2021-03-19 01:44:04
伟大的!当您确实需要计时器工作但不仅仅是修复一些动画问题时,应该使用这种方法!
2021-03-26 01:44:04
整洁的!现在让我们希望他们不会“解决”这个问题。
2021-04-05 01:44:04

有一个使用 Web Workers 的解决方案(如前所述),因为它们在单独的进程中运行并且不会减慢

我编写了一个小脚本,无需更改代码即可使用 - 它只是覆盖了函数 setTimeout、clearTimeout、setInterval、clearInterval。

只需在所有代码之前包含它。

更多信息在这里

我有点困惑 - 使用 React 你可以只用 NPM 安装它而你不需要导入任何东西吗?
2021-03-12 01:44:04
@neoDev 听起来很奇怪,因为我最近尝试过并且有效
2021-03-20 01:44:04
@neoDev 您是否在任何其他 JavaScript 之前加载 HackTimer.js(或 HackTimer.min.js)?
2021-03-22 01:44:04
@ruslan-tushov 刚刚在 chrome 60 上尝试过,但它似乎根本没有效果......我正在调用几个 setTimeout 中的几个函数来向 DOM 中添加/删除由 css 动画制作动画的元素,我看到它们被冻结了选项卡切换后有一些延迟(就像往常一样,没有插件)
2021-03-29 01:44:04
我已经在一个包括使用计时器的库的项目中对其进行了测试(因此我无法自己实现工作人员)。它就像一个魅力。感谢这个图书馆
2021-04-02 01:44:04

只需这样做:

var $div = $('div');
var a = 0;

setInterval(function() {
    a++;
    $div.stop(true,true).css("left", a);
}, 1000 / 30);

非活动浏览器选项卡会缓冲某些setIntervalsetTimeout功能。

stop(true,true) 将停止所有缓冲事件并仅立即执行最后一个动画。

window.setTimeout()方法现在限制为在非活动选项卡中每秒发送不超过一个超时。此外,它现在将嵌套超时限制为 HTML5 规范允许的最小值:4 毫秒(而不是它过去用于限制的 10 毫秒)。

很高兴知道 - 例如对于 ajax 更新,其中最新数据总是正确的 - 但是对于发布的问题,这是否意味着动画显着减慢/暂停,因为“a”不会增加适当的次数?
2021-03-28 01:44:04

双方setIntervalrequestAnimationFrame没有工作的时候标签是不活动或工作,但不是在正确的时间。一个解决方案是使用另一个时间事件源。例如,网络套接字网络工作者是两个在选项卡处于非活动状态时工作正常的事件源。因此无需将所有代码都移至 web worker,只需将 worker 用作时间事件源:

// worker.js
setInterval(function() {
    postMessage('');
}, 1000 / 50);

.

var worker = new Worker('worker.js');
var t1 = 0;
worker.onmessage = function() {
    var t2 = new Date().getTime();
    console.log('fps =', 1000 / (t2 - t1) | 0);
    t1 = t2;
}

此示例的jsfiddle 链接

“只需使用工人作为时间事件源”谢谢你的想法
2021-03-22 01:44:04
jsfiddle 给出了一个 SecurityError。
2021-04-04 01:44:04