如果Chrome中的标签处于非活动状态,如何才能使setInterval正常工作?

pim*_*vdb 171 javascript google-chrome setinterval

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

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

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

setInterval(function() {
    a++;
    $div.css("left", a)
}, 1000 / 30);
Run Code Online (Sandbox Code Playgroud)

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

我想这是通过设计完成的,以提高性能,但有没有办法禁用这种行为?在我的场景中,它实际上是一个缺点.

kbt*_*tzr 141

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

如果转换的值是使用帧之间经过的实时时间而不是每个时间间隔的固定增量来计算的,那么您不仅可以解决此问题,还可以通过使用requestAnimationFrame来实现更好的动画,因为如果处理器不是,则可以达到60fps很忙.

这是一个使用以下内容的动画属性转换的vanilla 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()
Run Code Online (Sandbox Code Playgroud)
body {
  overflow: hidden;
}

div {
    position: absolute;
    white-space: nowrap;
}
Run Code Online (Sandbox Code Playgroud)
<div id="target">...HERE WE GO</div>
Run Code Online (Sandbox Code Playgroud)


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

@UpTheCreek评论:

适用于演示文稿问题,但仍有一些事情需要继续运行.

如果您的后台任务需要按给定的时间间隔精确执行,则可以使用HTML5 Web Workers.请看下面的Möhre的答案,了解更多详情......

CSS vs JS"动画"

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


小智 67

我遇到了与音频褪色和HTML5播放器相同的问题.当标签变为不活动时,它被卡住了.所以我发现WebWorker可以无限制地使用间隔/超时.我用它来发布主要的javascript"ticks".

WebWorkers代码:

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);
Run Code Online (Sandbox Code Playgroud)

主要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;
    }
});
Run Code Online (Sandbox Code Playgroud)

  • 整齐!现在让我们希望他们不会"修复"这个问题. (4认同)
  • 大!当您确实需要计时器工作但不仅仅是修复一些动画问题时,应该使用这种方法! (2认同)

Rus*_*hov 35

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

我编写了一个可以在不更改代码的情况下使用的小脚本 - 它只是覆盖了函数setTimeout,clearTimeout,setInterval,clearInterval.

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

更多信息在这里

  • 我有点困惑 - 对于 React,你可以只用 NPM 安装它,而不需要导入任何东西吗? (3认同)

www*_*www 13

这样做:

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

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

非活动浏览器选项卡缓冲了一些setInterval或多个setTimeout功能.

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

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


ima*_*man 8

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

// worker.js
setInterval(function() {
    postMessage('');
}, 1000 / 50);
Run Code Online (Sandbox Code Playgroud)

.

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;
}
Run Code Online (Sandbox Code Playgroud)

此示例的jsfiddle 链接


Chr*_*itz 8

受到 Ruslan 答案的启发,您可以将一个方便的小类复制并粘贴到代码中作为以下内容的替换setInterval

class WorkerInterval {
  worker = null;
  constructor(callback, interval) {
    const blob = new Blob([`setInterval(() => postMessage(0), ${interval});`]);
    const workerScript = URL.createObjectURL(blob);
    this.worker = new Worker(workerScript);
    this.worker.onmessage = callback;
  }

  stop() {
    this.worker.terminate();
  }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

   const interval = new WorkerInterval(callback, 100);
   // sometime later
   interval.stop();
Run Code Online (Sandbox Code Playgroud)


小智 7

对我来说,像这里的其他人一样在后台播放音频并不重要,我的问题是我有一些动画,当你在其他选项卡中并回到它们时,它们表现得像疯了一样。如果阻止非活动选项卡,我的解决方案是将这些动画放入其中:

if (!document.hidden){ //your animation code here }
Run Code Online (Sandbox Code Playgroud)

多亏了我的动画只有在选项卡处于活动状态时才运行。我希望这会对我的情况有所帮助。


小智 5

我认为在这个例子中最好地理解这个问题:http://jsfiddle.net/TAHDb/

我在这做一件简单的事情:

间隔为1秒,每次隐藏第一个跨度并将其移动到最后,并显示第二个跨度.

如果你留在页面上它按预期工作.但如果你隐藏标签几秒钟,当你回来时,你会看到一个令人厌烦的东西.

就像你现在不活动期间没有发生的所有事件一样,这一切都将在一次完成.所以几秒钟后你就会得到X事件.它们如此之快,以至于可以同时看到所有6个跨度.

所以它接缝铬只会延迟事件,所以当你回来时所有事件都会发生但是一下子......

一个实际的应用程序就是这个简单的幻灯片.想象一下这些数字是图像,如果用户在回来时隐藏了标签隐藏,他会看到所有的模仿浮动,完全被吸引.

为了解决这个问题,请使用像pimvdb这样的stop(true,true).这将清除事件队列.

  • 实际上这是因为jQuery使用的`requestAnimationFrame`.由于它有一些怪癖(就像这个),他们删除了它.在1.7中你没有看到这种行为.http://jsfiddle.net/TAHDb/1/.因为间隔是每秒一次,这实际上不会影响我发布的问题,因为非活动标签的最大值也是每秒一次 - 所以没有区别. (4认同)