[javascript] How can I make setInterval also work when a tab is inactive in Chrome?

I have a setInterval running a piece of code 30 times a second. This works great, however when I select another tab (so that the tab with my code becomes inactive), the setInterval is set to an idle state for some reason.

I made this simplified test case (http://jsfiddle.net/7f6DX/3/):

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

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

If you run this code and then switch to another tab, wait a few seconds and go back, the animation continues at the point it was when you switched to the other tab. So the animation isn't running 30 times a second in case the tab is inactive. This can be confirmed by counting the amount of times the setInterval function is called each second - this will not be 30 but just 1 or 2 if the tab is inactive.

I guess that this is done by design so as to improve performance, but is there any way to disable this behaviour? It is actually a disadvantage in my scenario.

This question is related to javascript google-chrome setinterval

The answer is


On most browsers inactive tabs have low priority execution and this can affect JavaScript timers.

If the values of your transition were calculated using real time elapsed between frames instead fixed increments on each interval, you not only workaround this issue but also can achieve a smother animation by using requestAnimationFrame as it can get up to 60fps if the processor isn't very busy.

Here's a vanilla JavaScript example of an animated property transition using requestAnimationFrame:

_x000D_
_x000D_
var target = document.querySelector('div#target')_x000D_
var startedAt, duration = 3000_x000D_
var domain = [-100, window.innerWidth]_x000D_
var range = domain[1] - domain[0]_x000D_
_x000D_
function start() {_x000D_
  startedAt = Date.now()_x000D_
  updateTarget(0)_x000D_
  requestAnimationFrame(update)_x000D_
}_x000D_
_x000D_
function update() {_x000D_
  let elapsedTime = Date.now() - startedAt_x000D_
_x000D_
  // playback is a value between 0 and 1_x000D_
  // being 0 the start of the animation and 1 its end_x000D_
  let playback = elapsedTime / duration_x000D_
_x000D_
  updateTarget(playback)_x000D_
  _x000D_
  if (playback > 0 && playback < 1) {_x000D_
   // Queue the next frame_x000D_
   requestAnimationFrame(update)_x000D_
  } else {_x000D_
   // Wait for a while and restart the animation_x000D_
   setTimeout(start, duration/10)_x000D_
  }_x000D_
}_x000D_
_x000D_
function updateTarget(playback) {_x000D_
  // Uncomment the line below to reverse the animation_x000D_
  // playback = 1 - playback_x000D_
_x000D_
  // Update the target properties based on the playback position_x000D_
  let position = domain[0] + (playback * range)_x000D_
  target.style.left = position + 'px'_x000D_
  target.style.top = position + 'px'_x000D_
  target.style.transform = 'scale(' + playback * 3 + ')'_x000D_
}_x000D_
_x000D_
start()
_x000D_
body {_x000D_
  overflow: hidden;_x000D_
}_x000D_
_x000D_
div {_x000D_
    position: absolute;_x000D_
    white-space: nowrap;_x000D_
}
_x000D_
<div id="target">...HERE WE GO</div>
_x000D_
_x000D_
_x000D_


For Background Tasks (non-UI related)

@UpTheCreek comment:

Fine for presentation issues, but still there are some things that you need to keep running.

If you have background tasks that needs to be precisely executed at given intervals, you can use HTML5 Web Workers. Take a look at Möhre's answer below for more details...

CSS vs JS "animations"

This problem and many others could be avoided by using CSS transitions/animations instead of JavaScript based animations which adds a considerable overhead. I'd recommend this jQuery plugin that let's you take benefit from CSS transitions just like the animate() methods.


I think that a best understanding about this problem is in this example: http://jsfiddle.net/TAHDb/

I am doing a simple thing here:

Have a interval of 1 sec and each time hide the first span and move it to last, and show the 2nd span.

If you stay on page it works as it is supposed. But if you hide the tab for some seconds, when you get back you will see a weired thing.

Its like all events that didn't ucur during the time you were inactive now will ocur all in 1 time. so for some few seconds you will get like X events. they are so quick that its possible to see all 6 spans at once.

So it seams chrome only delays the events, so when you get back all events will occur but all at once...

A pratical application were this ocur iss for a simple slideshow. Imagine the numbers being Images, and if user stay with tab hidden when he came back he will see all imgs floating, Totally mesed.

To fix this use the stop(true,true) like pimvdb told. THis will clear the event queue.


For me it's not important to play audio in the background like for others here, my problem was that I had some animations and they acted like crazy when you were in other tabs and coming back to them. My solution was putting these animations inside if that is preventing inactive tab:

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

thanks to that my animation was running only if tab was active. I hope this will help someone with my case.


Just do this:

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

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

Inactive browser tabs buffer some of the setInterval or setTimeout functions.

stop(true,true) will stop all buffered events and execute immediatly only the last animation.

The window.setTimeout() method now clamps to send no more than one timeout per second in inactive tabs. In addition, it now clamps nested timeouts to the smallest value allowed by the HTML5 specification: 4 ms (instead of the 10 ms it used to clamp to).


Both setInterval and requestAnimationFrame don't work when tab is inactive or work but not at the right periods. A solution is to use another source for time events. For example web sockets or web workers are two event sources that work fine while tab is inactive. So no need to move all of your code to a web worker, just use worker as a time event source:

// 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 of this sample.


Here's my rough solution

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

I was able to call my callback function at minimum of 250ms using audio tag and handling its ontimeupdate event. Its called 3-4 times in a second. Its better than one second lagging setTimeout


I ran into the same problem with audio fading and HTML5 player. It got stuck when tab became inactive. So I found out a WebWorker is allowed to use intervals/timeouts without limitation. I use it to post "ticks" to the main javascript.

WebWorkers Code:

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

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

Heavily influenced by Ruslan Tushov's library, I've created my own small library. Just add the script in the <head> and it will patch setInterval and setTimeout with ones that use WebWorker.


Playing an audio file ensures full background Javascript performance for the time being

For me, it was the simplest and least intrusive solution - apart from playing a faint / almost-empty sound, there are no other potential side effects

You can find the details here: https://stackoverflow.com/a/51191818/914546

(From other answers, I see that some people use different properties of the Audio tag, I do wonder whether it's possible to use the Audio tag for full performance, without actually playing something)


Note: this solution is not suitable if you like your interval works on the background, for example, playing audio or ... but if you are confused for example about your animation not working properly when coming back to your page(tab) this is a good solution.

There are many ways to achieve this goal, maybe the "WebWorkers" is the most standard one but certainly, it's not the easiest and handy one, especially If you don't have enough Time, so you can try this way:

?BASIC CONCEPT:

1- build a name for your interval(or animation) and set your interval(animation), so it would run when user first time open your page : var interval_id = setInterval(your_func, 3000);

2- by $(window).focus(function() {}); and $(window).blur(function() {}); you can clearInterval(interval_id) everytime browser(tab) is deactived and ReRun your interval(animation) everytime browser(tab) would acive again by interval_id = setInterval();

?SAMPLE CODE:

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

There is a solution to use Web Workers (as mentioned before), because they run in separate process and are not slowed down

I've written a tiny script that can be used without changes to your code - it simply overrides functions setTimeout, clearTimeout, setInterval, clearInterval.

Just include it before all your code.

more info here


It is quite old question but I encountered the same issue.
If you run your web on chrome, you could read through this post Background Tabs in Chrome 57 .

Basically the interval timer could run if it haven't run out of the timer budget.
The consumption of budget is based on CPU time usage of the task inside timer.
Based on my scenario, I draw video to canvas and transport to WebRTC.
The webrtc video connection would keep updating even the tab is inactive.

However you have to use setInterval instead of requestAnimationFrame.
it is not recommended for UI rendering though.

It would be better to listen visibilityChange event and change render mechenism accordingly.

Besides, you could try @kaan-soral and it should works based on the documentation.


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to google-chrome

SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 81 SameSite warning Chrome 77 What's the net::ERR_HTTP2_PROTOCOL_ERROR about? session not created: This version of ChromeDriver only supports Chrome version 74 error with ChromeDriver Chrome using Selenium Jupyter Notebook not saving: '_xsrf' argument missing from post How to fix 'Unchecked runtime.lastError: The message port closed before a response was received' chrome issue? Selenium: WebDriverException:Chrome failed to start: crashed as google-chrome is no longer running so ChromeDriver is assuming that Chrome has crashed WebDriverException: unknown error: DevToolsActivePort file doesn't exist while trying to initiate Chrome Browser How to make audio autoplay on chrome How to handle "Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first." on Desktop with Chrome 66?

Examples related to setinterval

How to make `setInterval` behave more in sync, or how to use `setTimeout` instead? How can I pause setInterval() functions? Can clearInterval() be called inside setInterval()? Stop setInterval clearInterval() not working Calculating Page Load Time In JavaScript Javascript setInterval not working How to start and stop/pause setInterval? How do I reset the setInterval timer? jquery function setInterval