如何使 setInterval 在 Chrome 中的标签页处于非活动状态时也能正常工作?

2022-08-30 00:38:18

我有一个每秒运行30次代码。这很有效,但是当我选择另一个选项卡(以便包含我的代码的选项卡变为非活动状态)时,由于某种原因,将其设置为空闲状态。setIntervalsetInterval

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

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

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

如果运行此代码,然后切换到另一个选项卡,请等待几秒钟然后返回,动画将在切换到另一个选项卡时的位置继续。

因此,如果选项卡处于非活动状态,动画不会每秒运行 30 次。这可以通过计算每秒调用函数的次数来确认 - 如果选项卡处于非活动状态,则不会是30,而只是1或2。setInterval

我想这是通过设计来完成的,以提高系统性能,但是有没有办法禁用此行为?

这实际上是我场景中的一个缺点。


答案 1

在大多数浏览器上,非活动选项卡的执行优先级较低,这可能会影响 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与JS“动画”

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


答案 2

我在音频褪色和HTML5播放器方面遇到了同样的问题。当选项卡变为非活动状态时,它卡住了。所以我发现WebWorker可以不受限制地使用间隔/超时。我用它来将“ticks”发布到主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;
    }
});