【问题标题】:How can I make setInterval also work when a tab is inactive in Chrome?当 Chrome 中的选项卡处于非活动状态时,如何使 setInterval 也起作用?
【发布时间】:2025-12-25 19:45:07
【问题描述】:

我有一个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 次。

我猜这是为了提高系统性能而设计的,但是有什么办法可以禁用这种行为吗?

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

【问题讨论】:

  • 可能不会,除非您将它与Date 对象一起破解以真正查看时间已过。
  • 您能详细解释一下您的方案吗?也许这不是一个缺点。
  • 您的意思是 this 代码更改,并且您的问题也在讨论 here - Oliver Mattos 发布了一些解决方法,也许它对您的情况也有效?
  • Date 中读取的,几乎总是最好根据动画开始后经过的实时时间来设置关键动画。因此,当间隔没有快速触发(由于这个或其他原因)时,动画只会变得更不稳定,而不是更慢。
  • 问题的标题把我引到这里,但是我的用例有点不同——我需要刷新身份验证令牌,而不管选项卡是否处于非活动状态。如果这与您相关,请查看我的回答 here

标签: javascript google-chrome setinterval


【解决方案1】:

在大多数浏览器上,非活动标签的执行优先级较低,这会影响 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;
}
&lt;div id="target"&gt;...HERE WE GO&lt;/div&gt;

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

@UpTheCreek 评论:

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

如果您有需要以给定间隔精确执行的后台任务,您可以使用HTML5 Web Workers。更多详情请查看Möhre's answer below...

CSS 与 JS “动画”

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

【讨论】:

  • 我在 FireFox 5 上试过这个,即使标签不在焦点上,'setInterva'l 仍然运行。我在“setInterval”中的代码使用“animate()”幻灯片放映幻灯片。看起来动画是由 FireFox 排队的。当我返回选项卡时,FireFox 会一个接一个地立即弹出队列,导致幻灯片快速移动,直到队列为空。
  • @nthpixel 会在 tat 情况下添加 .stop (根据 www 的回答)帮助(因为每次它都会清除以前的动画)
  • @gordatron - .stop 对我的情况没有帮助。相同的行为。我现在使用的是 FireFox 6.0.2。我想知道这是不是 jQuery 实现 .animate 的方式。
  • @cvsguimaraes - 是的,好点。您是否知道规范中的任何内容可以保证 Web 工作者即使在后台选项卡中也能运行?我有点担心浏览器供应商将来也会武断地决定钳制这些......
  • 我会将代码的最后一行更改为 before = now; 以获取自 start 而不是 end 以来经过的时间上一次通话。这通常是您在制作动画时想要的。如今,如果执行间隔函数需要15ms,则 elapsedTime将给出35ms(而不是50ms,是间隔的50ms),下次调用它(当Tab处于活动状态时)。
【解决方案2】:

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

【讨论】:

  • 整洁!现在让我们希望他们不会“解决”这个问题。
  • 太棒了!当您确实需要计时器工作而不只是修复一些动画问题时,应该使用这种方法!
  • 我用这个做了一个很棒的节拍器。有趣的是,所有最流行的 html5 鼓机都没有使用这种方法,而是使用劣质的常规 setTimeout。 skyl.github.io/grimoire/fretboard-prototype.html - chrome 或 safari 现在玩得最好。
  • 如果开发者开始滥用它,他们会“修复”它。除了极少数例外,您的应用并不那么重要,它应该在后台消耗资源以提供对用户体验的一些最小改进。
  • @Gunchars 你说的很对,但如果应用程序可以在这方面征求用户的许可不是更好吗?如果用户愿意允许这种情况发生,那为什么不呢?
【解决方案3】:

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

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

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

more info here

【讨论】:

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

这样做:

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”不会增加了适当的次数?
【解决方案5】:

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

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

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

【讨论】:

  • 在复杂的答案中,这个简单的解决方案派上了用场。谢谢。
【解决方案6】:

setIntervalrequestAnimationFrame 在选项卡处于非活动状态或工作但不在正确的时间段时都不起作用。一种解决方案是使用另一个时间事件源。例如 web socketsweb workers 是两个在选项卡处于非活动状态时可以正常工作的事件源。因此无需将所有代码移动到 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 这个样本。

【讨论】:

  • " 仅使用 worker 作为时间事件源" 感谢您的想法
  • jsfiddle 给出了 SecurityError。
【解决方案7】:

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

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

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

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

就像您现在不活动期间未发生的所有事件将在 1 次内发生。所以几秒钟你会得到像 X 事件。它们是如此之快,以至于可以一次看到所有 6 个跨度。

所以它接缝 chrome 只会延迟事件,所以当你回来时,所有事件都会同时发生......

这是一个实用的应用程序,用于制作简单的幻灯片。想象一下数字是图像,如果用户在他回来时仍然隐藏标签,他会看到所有 imgs 浮动,完全混乱。

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

【讨论】:

  • 事实上这是因为 jQuery 使用了requestAnimationFrame。由于它有一些怪癖(比如这个),他们删除了它。在 1.7 中,您看不到这种行为。 jsfiddle.net/TAHDb/1。因为间隔是每秒一次,这实际上不会影响我发布的问题,因为非活动标签的最大值也是每秒一次 - 所以没有区别。
【解决方案8】:

深受 Ruslan Tushov 图书馆的影响,我创建了自己的小型 library。只需在&lt;head&gt; 中添加脚本,它就会用使用WebWorker 的脚本修补setIntervalsetTimeout

【讨论】:

  • 刚刚在 chrome 60 上尝试过,但它似乎根本没有效果......我从几个 setTimeout 调用几个函数来添加/删除由 css 动画动画的 DOM 元素,并且我看到它们在标签切换后冻结了一些延迟(就像没有插件一样)
  • 我在 Chrome 60 上使用它。你能做一个演示并发布在 github 问题上吗?
【解决方案9】:

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

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

您可以在这里找到详细信息:https://*.com/a/51191818/914546

(从其他答案中,我看到有些人使用 Audio 标签的不同属性,我想知道是否可以在不实际播放的情况下使用 Audio 标签来获得完整的性能)

【讨论】:

    【解决方案10】:

    我在这里为任何试图在计时器函数中解决此问题的人提供了一个简单的解决方案,其中 @kbtzr mentioned in another answer 我们可以使用 Date 对象而不是固定增量来计算自开始以来经过的时间,即使您不在应用程序的选项卡中,它也可以工作。

    这是示例 HTML。

    <body>
      <p id="time"></p>
    </body>
    

    然后是这个 JavaScript:

    let display = document.querySelector('#time')
    let interval
    let time
    function startTimer() {
        let initialTime = new Date().getTime()
        interval = setInterval(() => {
            let now = new Date().getTime()
            time = (now - initialTime) + 10
            display.innerText = `${Math.floor((time / (60 * 1000)) % 60)}:${Math.floor((time / 1000) % 60)}:${Math.floor((time / 10) % 100)}`
        }, 10)
    }
    startTimer()
    

    这样,即使由于非活动选项卡的性能原因而增加了间隔值,所做的计算也将保证正确的时间。这是一个普通代码,但我在我的 React 应用程序中使用了这个逻辑,你也可以根据需要修改它。

    【讨论】:

      【解决方案11】:

      这是一个很老的问题,但我遇到了同样的问题。

      如果您在 chrome 上运行您的网站,您可以阅读这篇文章 Background Tabs in Chrome 57

      如果没有用完计时器预算,间隔计时器基本上可以运行。

      预算的消耗取决于定时器内任务的CPU时间使用情况。

      根据我的场景,我将视频绘制到画布并传输到 WebRTC。

      即使标签页处于非活动状态,webrtc 视频连接也会不断更新。

      但是您必须使用setInterval 而不是requestAnimationFrame,但不建议在 UI 渲染中使用它。

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

      此外,您可以尝试Kaan Soral suggests,它应该可以根据文档工作。

      【讨论】:

      • 但如果选项卡处于非活动状态,setInterval 不起作用
      • @KapilSoni 嘿,你有没有找到任何解决方案的人,请评论..
      • @PawanDeore 我使用了 rxjs 间隔(1000)而不是那个
      【解决方案12】:

      注意:如果您希望间隔在后台工作,例如播放音频或其他内容,则此解决方案不适合。但是,如果您对返回页面或标签时动画无法正常工作感到困惑,这是一个很好的解决方案。

      有很多方法可以实现这个目标,也许“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;
      });
      

      【讨论】:

        【解决方案13】:

        这是我的粗略解决方案

        (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 的无限循环。当它无限循环时,它会检查每个事件处理是否有超时或间隔函数要运行。
        【解决方案14】:

        我能够使用音频标签在至少 250 毫秒内调用我的回调函数并处理其 ontimeupdate 事件。它在一秒钟内调用 3-4 次。它优于一秒的 setTimeout

        【讨论】:

          【解决方案15】:

          这个问题有一个解决方法,虽然实际上标签必须在某些窗口中激活

          • 将非活动标签页设为单独的浏览器窗口。
          • 不要使任何其他窗口最大化(除非最大化的窗口在您的后面)。

          这应该给浏览器一种始终处于活动状态的印象。

          这有点麻烦,但也是一个快速的胜利。前提是您可以控制窗口排列。

          【讨论】:

          • 虽然这可能是一个不错的最终用户组合,但这是一个以编程为中心的解决方案吗?
          最近更新 更多