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

时间:2011-05-08 11:58:41

标签: 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);

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

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

14 个答案:

答案 0 :(得分:139)

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

如果转换的值是使用帧之间的实时时间计算的,而是在每个时间间隔上固定增量,那么您不仅可以解决此问题,还可以使用{{3}来实现更好的动画效果如果处理器不是很忙,它可以达到60fps。

以下是使用requestAnimationFrame的动画属性转换的vanilla JavaScript示例:

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评论:

  

很好的演示问题,但仍然   有一些事情你需要继续运行。

如果您有所需的后台任务以给定的时间间隔精确执行,则可以使用requestAnimationFrame。请查看HTML5 Web Workers了解更多详情......

CSS vs JS“动画”

使用CSS过渡/动画而不是基于JavaScript的动画可以避免此问题和Möhre's answer below,从而增加了相当大的开销。我推荐这个many others让你从CSS转换中受益,就像animate()方法一样。

答案 1 :(得分:66)

我遇到了与音频褪色和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);

主要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;
    }
});

答案 2 :(得分:33)

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

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

只需在所有代码之前加入它。

more info here

答案 3 :(得分:13)

这样做:

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 ms(而不是用于钳位的10 ms)。

答案 4 :(得分:5)

我认为在此示例中对此问题有最好的理解:http://jsfiddle.net/TAHDb/

我在做一件简单的事情:

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

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

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

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

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

要解决此问题,请使用像pimvdb一样的stop(true,true)。 这将清除事件队列。

答案 5 :(得分:2)

对我来说,像在这里播放其他音频一样在后台播放音频并不重要,我的问题是我有一些动画,当您在其他选项卡中又回到它们时,它们表现得很疯狂。我的解决方案是将这些动画放入 if 中,以防止无效标签

if (!document.hidden){ //your animation code here }

感谢我的动画在处于活动状态时仅运行。 我希望这会对我的案子有所帮助。

答案 6 :(得分:1)

受到Ruslan Tushov图书馆的极大影响,我创造了自己的小library。只需在<head>中添加脚本,它就可以使用setInterval修补setTimeoutWebWorker

答案 7 :(得分:0)

这是我的粗略解决方案

RxTableViewReactiveArrayDataSource

答案 8 :(得分:0)

播放音频文件可确保暂时具有完整的后台Java脚本性能

对我来说,这是最简单且干扰最小的解决方案-除了播放微弱/几乎为空的声音外,没有其他潜在的副作用

您可以在此处找到详细信息:https://stackoverflow.com/a/51191818/914546

(从其他答案中,我看到有些人使用Audio标记的不同属性,我确实想知道是否可以使用Audio标记来发挥全部性能,而无需实际播放某些东西)

答案 9 :(得分:0)

在选项卡处于非活动状态或工作时,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 link此示例。

答案 10 :(得分:0)

注意:如果您希望间隔在背景上播放(例如播放音频或...),但是如果您对例如动画在以下情况下无法正常播放感到困惑,则此解决方案不适用回到您的页面(标签),这是一个很好的解决方案。

有很多方法可以实现这一目标,也许“ WebWorkers”是最标准的方法。但是当然,这不是最简单方便的方法,尤其是如果您没有足够的时间,因此您可以尝试这种方式:

►BASICCONCEPT:

1-为您的间隔(或动画)建立一个名称并设置间隔(动画),这样它将在用户首次打开您的页面时运行:var interval_id = setInterval(your_func, 3000);

2-通过$(window).focus(function() {});$(window).blur(function() {});clearInterval(interval_id)可以在每次停用浏览器(tab)时运行,并且每次{{1 }}

►示例代码:

interval_id = setInterval();

答案 11 :(得分:0)

我认为使用jQuery 3.3.x现在最简单的方法是使用此功能:

intervalFunction();

$([window, document]).on('focusin', function(){
    if (!intervalId){
        intervalFunction();
    }
}).on('focusout', function(){
    if (intervalId) {
        clearInterval(intervalId);
        intervalId = undefined;
    }
});

function intervalFunction() {
    // Your function hire
}

intervalFunction()会在页面加载时立即启动,然后如果焦点又回到了您尝试重新激活间隔,但是如果页面失去焦点,则可以随时返回到此。

清晰,轻松。此外,该解决方案不使用.focus().focusin().focusout()

之类的已弃用功能

答案 12 :(得分:0)

这是一个很老的问题,但是我遇到了同样的问题。
如果您在chrome上运行网络,则可以阅读这篇文章Background Tabs in Chrome 57

基本上,如果间隔计时器没有用完计时器预算,则间隔计时器可以运行。
预算消耗基于计时器内任务的CPU时间使用情况。
根据我的情况,我将视频绘制到画布上并传输到WebRTC。
即使选项卡处于非活动状态,webrtc视频连接也将不断更新。

但是您必须使用setInterval而不是requestAnimationFrame
不过,建议不要将其用于UI渲染。

最好听visibilityChange事件并相应地更改渲染机制。

此外,您可以尝试@ kaan-soral,它应该基于文档有效。

答案 13 :(得分:-1)

我能够使用音频标签在最短250ms内调用我的回调函数并处理其ontimeupdate事件。它在一秒钟内被称为3-4次。它优于一秒滞后的setTimeout