When using setInterval, if I switch tabs in Chrome and go back, the slider goes crazy catching up

Solution 1:

How to detect when a tab is focused or not in Chrome with Javascript?

window.addEventListener('focus', function() {
    document.title = 'focused';
},false);

window.addEventListener('blur', function() {
    document.title = 'not focused';
},false);

To apply to your situation:

var autopager;
function startAutopager() {
    autopager = window.setInterval(nextImage, 8000);
}
function stopAutopager() {
    window.clearInterval(autopager);
}

window.addEventListener('focus', startAutopager);    
window.addEventListener('blur', stopAutopager);

Note that in the latest version of Chromium, there is either a bug or a 'feature' which is making this less reliable, requiring that the user has clicked at least once anywhere in the window. See linked question above for details.

Solution 2:

I post an answer here: How can I make setInterval also work when a tab is inactive in Chrome?

Just do this:

setInterval(function() {
    $("#your-image-container").stop(true,true);
    nextImage();

}, 1000);

inactive browser tabs buffer some of the setInterval or setTimeout functions. stop(true,true) - will stop all buffered events and execute immadietly only 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).

Solution 3:

A few ideas comes to mind:


Idea #1

You can make it so that a short burst is idempotent. For example, you could say:

function now() {
    return (new Date()).getTime();
}

var autopagerInterval = 8000;

function startAutopager() {
    var startImage = getCurrentImageNumber();
    var startTime = now();

    var autopager = setInterval(
        function() {
            var timeSinceStart = now() - startTime();
            var targetImage = getCurrentImageNumber + Math.ceil(timeSinceStart/autopagerInterval);
            if (getCurrentImageNumber() != targetImage)
                setImageNumber(targetImage);  // trigger animation, etc.
        },
        autopagerInterval
    );
    return autopager;
}

This way even if the function runs 1000 times, it will still run in only a few milliseconds and animate only once.

note: If the user leaves the page and comes back, it will have scrolled. This is probably not what the original poster wants, but I leave this solution up since it is sometimes what you want.


Idea #2

Another way to add idempotence (while still keeping your nextImage() function and not having it scroll to the bottom of the page) would be to have the function set a mutex lock which disappears after a second (cleared by another timeout). Thus even if the setInterval function was called 1000 times, only the first instance would run and the others would do nothing.

var locked = false;
var autopager = window.setInterval(function(){
    if (!locked) {
        locked = true;
        window.setTimeout(function(){
            locked=false;
        }, 1000);
        nextImage();
    }
}, 8000);

edit: this may not work, see below


Idea #3

I tried the following test:

function f() {
    console.log((new Date()) + window.focus());
    window.setTimeout(f, 1000);
}
f();

It seems to indicate that the function is being called every second. This is odd... but I think this means that the callbacks are being called, but that the page renderer refuses to update the page in any graphical way while the tab is unfocused, delaying all operations until the user returns, but operations keep piling up.

Also the window.focus() function doesn't say if the window has focus; it GIVES focus to the window, and is thus irrelevant.

What we want is probably this: How to detect when a tab is focused or not in Chrome with Javascript? -- you can unset your interval when the window loses focus (blur), and reset it when it gains focus.

Solution 4:

I don't know exactly what is going on in your function nextImage(), but I had a similar issue. I was using animate() with setInterval() on a jQuery image slider that I created, and I was experiencing the same thing as you when I switched to a different tab and back again. In my case the animate() function was being queued, so once the window regained focus the slider would go crazy. To fix this I just stopped the animate() function from queuing.

There are a couple ways you can do this. the easiest is with .stop(), but this issue and ways to fix it are documented in the jQuery docs. Check this page near the bottom under the heading additional notes: http://api.jquery.com/animate/