当Chrome中的标签页处于非活动状态时,如何使setInterval也起作用?


189

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。

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


3
可能不是,除非您与Date对象一起砍掉它,以真正了解已经过去了多少时间。
Alex

您能否进一步说明您的情况?也许这不是一个缺点。
詹姆斯

2
您的意思是代码更改,并且您的要求也在此处讨论-Oliver Mattos发布了一些解决方法,也许在您的情况下也有效?
暗影巫师为您耳边

6
从动画开始以来,几乎总是最好将动画开始后经过的实时时间作为关键动画的关键Date。这样,当间隔不会很快触发时(出于这种原因或其他原因),动画只会变得抖动,而不是变慢。
bobince 2011年

1
问题的标题将我引到了这里,但是我的用例有些不同-无论选项卡处于不活动状态,我都需要刷新身份验证令牌。如果这与您相关,请在此处
Erez Cohen

Answers:


152

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

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

这是使用以下内容进行动画属性转换的requestAnimationFrame原始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评论:

对于演示文稿问题很好,但是仍然需要保持一些运行状态。

如果您有需要按给定间隔精确执行的后台任务,则可以使用HTML5 Web Workers。看看下面的Möhre答案有关更多详细信息, ...

CSS vs JS“动画”

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


1
我在FireFox 5上尝试过此操作,即使选项卡不清晰,“ setInterva”仍会运行。我在“ setInterval”中的代码使用“ animate()”滑动了幻灯片。看起来动画是由FireFox排队的。当我回到选项卡时,FireFox一次又一次弹出队列,导致幻灯片快速移动,直到队列为空。
nthpixel

@nthpixel将添加.stop(根据www的答案)来帮助达人(每次清除之前的动画时)

@gordatron-.stop对我的处境没有帮助。行为相同。我现在正在使用FireFox 6.0.2。我想知道这是否是jQuery实现.animate的方式。
nthpixel's

@cvsguimaraes-是的,很好。您是否了解规范中有任何保证即使在后台选项卡中Web工作者也可以运行的信息?我有点担心浏览器供应商将来也会任意决定限制它们……
UpTheCreek 2013年

我将更改要读取的代码的倒数第二行,before = now;以获取自上次调用开始而不是结束之前经过的时间。这通常是在为事物设置动画时想要的。现在,如果执行间隔函数需要15 elapsedTime毫秒,则下次调用该选项卡时(激活选项卡时)将给出35毫秒(而不是间隔的50毫秒)。
乔纳斯·柏林

71

我在音频淡入和HTML5播放器中遇到了同样的问题。选项卡不活动时卡住了。因此,我发现WebWorker可以不受限制地使用间隔/超时。我用它发布“滴答”到主要的JavaScript。

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

6
整齐!现在,让我们希望他们不会“解决”此问题。
pimvdb 2012年

2
大!当您确实需要计时器工作时,应该使用这种方法,而不仅仅是解决某些动画问题!
Konstantin Smolyanin

1
我做了一个很棒的节拍器。有趣的是,所有最流行的html5鼓机都不使用此方法,而是使用次等的setTimeout。skyl.github.io/grimoire/fretboard-prototype.html-chrome或safari现在发挥得最好。
Skylar Saveland

如果开发人员开始滥用它,他们将“修复”它。除极少数情况外,您的应用程序并不重要,因为它应该在后台消耗资源以使用户体验得到最小的改善。
Gunchars '19

41

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

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

只需在所有代码之前添加它即可。

更多信息在这里


1
我已经在一个包含使用计时器的库的项目中对其进行了测试(因此我自己无法实现worker)。它就像一种魅力。感谢您的图书馆
ArnaudR 2015年

@ ruslan-tushov刚刚在chrome 60上尝试了它,但似乎根本没有效果...我正在调用几个setTimeout中的几个函数,以将由CSS动画制作的元素添加/删除到DOM中,我看到它们冻结了选项卡切换后有一些延迟(与往常一样没有插件)
neoDev

@neoDev是否在其他JavaScript之前加载HackTimer.js(或HackTimer.min.js)?
Davide Patti

@neoDev听起来真的很奇怪,因为我最近尝试了并且可以正常工作
Davide Patti

@DurdenP是的,我也尝试将其放在其他任何JavaScript之前。也许是因为我正在使用CSS动画?我记得我也尝试过网络工作者,但没有运气
neoDev

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毫秒(而不是原来的10毫秒)。


1
知道这一点很好-例如,对于最新数据始终正确的Ajax更新-但对于所发布的问题,这并不意味着动画会明显减慢/暂停,因为“ a”不会增加适当的次数?

5

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

我在这里做一个简单的事情:

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

如果您停留在页面上,它将按预期工作。但是,如果您将标签页隐藏了几秒钟,那么当您回来时,您会看到一个奇怪的东西。

就像您在闲置期间未发生的所有事件一样,它会在1次内全部发生。所以几秒钟后,您将获得X事件。它们是如此之快,以至于可以一次看到所有6个跨度。

因此,它暗示Chrome仅会延迟事件,因此当您取回所有事件时,所有事件都会发生,但是所有事件都会一次出现...

这是一个简单的幻灯片演示的实际应用。想象一下数字是图像,如果用户回来时保持隐藏的选项卡,他将看到所有img浮动,完全混乱。

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


4
实际上,这是因为使用requestAnimationFrame了jQuery。由于它的某些怪异之处(例如这一点),他们将其删除。在1.7中,您看不到这种行为。jsfiddle.net/TAHDb/1。因为间隔是每秒一次,所以这实际上不会影响我发布的问题,因为不活动的选项卡的最大值也是每秒一次-因此没有区别。
pimvdb

4

对我来说,像在这里播放其他音频一样在后台播放音频并不重要,我的问题是我有一些动画,当您在其他选项卡中又回到它们时,它们表现得很疯狂。我的解决办法是把里面这些动画如果这是预防活动标签

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

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


1
我这样使用它setInterval(() => !document.hidden && myfunc(), 1000),效果很好
Didi Bear

4

双方setIntervalrequestAnimationFrame没有工作的时候标签是不活动或工作,但不是在正确的时间。一种解决方案是将其他源用于时间事件。例如,Web套接字Web Worker是两个事件源,它们在tab不活动时可以正常工作。因此,无需将所有代码移至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链接


“仅将工作人员用作时间事件的来源”感谢您的想法
Promod Sampath Elvitigala

1

在Ruslan Tushov的图书馆的影响下,我创建了自己的小型图书馆。只需在中添加脚本,<head>它将使用setIntervalsetTimeout进行修补WebWorker


刚在chrome 60上尝试过,但似乎根本没有效果...我正在从几个setTimeout调用几个函数,以将元素添加/删除到由CSS动画制作的DOM中,并且我看到它们在冻结之后有些延迟选项卡开关(与往常一样没有插件)
neoDev

我在Chrome 60上使用它。您可以制作一个演示并将其发布在github问题上吗?
马里

1

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

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

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

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


0

这是我的粗略解决方案

(function(){
var index = 1;
var intervals = {},
    timeouts = {};

function postMessageHandler(e) {
    window.postMessage('', "*");

    var now = new Date().getTime();

    sysFunc._each.call(timeouts, function(ind, obj) {
        var targetTime = obj[1];

        if (now >= targetTime) {
            obj[0]();
            delete timeouts[ind];
        }
    });
    sysFunc._each.call(intervals, function(ind, obj) {
        var startTime = obj[1];
        var func = obj[0];
        var ms = obj[2];

        if (now >= startTime + ms) {
            func();
            obj[1] = new Date().getTime();
        }
    });
}
window.addEventListener("message", postMessageHandler, true);
window.postMessage('', "*");

function _setTimeout(func, ms) {
    timeouts[index] = [func, new Date().getTime() + ms];
    return index++;
}

function _setInterval(func, ms) {
    intervals[index] = [func, new Date().getTime(), ms];
    return index++;
}

function _clearInterval(ind) {
    if (intervals[ind]) {
        delete intervals[ind]
    }
}
function _clearTimeout(ind) {
    if (timeouts[ind]) {
        delete timeouts[ind]
    }
}

var intervalIndex = _setInterval(function() {
    console.log('every 100ms');
}, 100);
_setTimeout(function() {
    console.log('run after 200ms');
}, 200);
_setTimeout(function() {
    console.log('closing the one that\'s 100ms');
    _clearInterval(intervalIndex)
}, 2000);

window._setTimeout = _setTimeout;
window._setInterval = _setInterval;
window._clearTimeout = _clearTimeout;
window._clearInterval = _clearInterval;
})();

postMessageHandler调用程序将调用它自己处理的事件,从而有一个无限循环,不会阻塞UI。而且,当它无限循环时,它正在与每个事件处理一起检查是否有超时或间隔函数要运行。
傻了

0

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

有很多方法可以实现这一目标,也许“ WebWorkers”是最标准的方法,但是可以肯定,它不是最简单方便的方法,尤其是如果您没有足够的时间,可以尝试以下方法:

►基本概念:

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

2- $(window).focus(function() {});$(window).blur(function() {});您可以clearInterval(interval_id)每次停用浏览器(选项卡)并且每次重新运行间隔(动画)时,浏览器(选项卡)将再次激活interval_id = setInterval();

►样本代码:

var interval_id = setInterval(your_func, 3000);

$(window).focus(function() {
    interval_id = setInterval(your_func, 3000);
});
$(window).blur(function() {
    clearInterval(interval_id);
    interval_id = 0;
});

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()


0

这是一个很老的问题,但我遇到了同样的问题。
如果您在Chrome上运行网络,则可以阅读Chrome 57中的“背景标签”一文

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

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

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

此外,您可以尝试@ kaan-soral,它应该根据文档工作。


-1

我能够使用音频标签并处理其ontimeupdate事件至少在250ms内调用回调函数。一秒钟内调用了3-4次。它比一秒钟落后setTimeout好

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.