web-dev-qa-db-fra.com

Comment puis-je faire en sorte que setInterval fonctionne également lorsqu'un onglet est inactif dans Chrome?

J'ai une setInterval qui exécute un morceau de code 30 fois par seconde. Cela fonctionne très bien, mais lorsque je sélectionne un autre onglet (de sorte que l'onglet avec mon code devienne inactif), la setInterval est définie sur un état inactif pour une raison quelconque.

J'ai fait ce cas de test simplifié ( http://jsfiddle.net/7f6DX/3/ ):

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

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

Si vous exécutez ce code puis passez à un autre onglet, attendez quelques secondes et revenez en arrière, l'animation se poursuit à l'endroit où vous vous trouviez lorsque vous avez basculé sur l'autre onglet. L'animation ne s'exécute donc pas 30 fois par seconde au cas où l'onglet serait inactif. Cela peut être confirmé en comptant le nombre de fois où la fonction setInterval est appelée chaque seconde - ce ne sera pas 30, mais seulement 1 ou 2 si l'onglet est inactif.

Je suppose que cela est fait par la conception afin d’améliorer les performances, mais existe-t-il un moyen de désactiver ce comportement? C'est en réalité un désavantage dans mon scénario.

159
pimvdb

Sur la plupart des navigateurs, les onglets inactifs ont une exécution de priorité basse, ce qui peut affecter les timers JavaScript.

Si les valeurs de votre transition ont été calculées en utilisant temps réel écoulé entre les images au lieu d'incréments fixes sur chaque intervalle, vous pouvez non seulement contourner ce problème, mais également obtenir une animation plus floue en utilisant requestAnimationFrame as il peut atteindre 60 images par seconde si le processeur n'est pas très occupé.

Voici un exemple JavaScript Vanilla d'une transition de propriété animée utilisant requestAnimationFrame:

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>


Pour les tâches en arrière-plan (non liées à l'interface utilisateur)

Commentaire @UpTheCreek:

Très bien pour les problèmes de présentation, mais quand même il y a certaines choses dont vous avez besoin pour continuer à courir.

Si vous avez des tâches en arrière-plan qui a besoin que soit exécuté avec précision à des intervalles donnés, vous pouvez utiliser Travailleurs Web HTML5 . Jetez un coup d'œil à La réponse de Möhre ci-dessous pour plus de détails ...

CSS vs JS "animations"

Ce problème et beaucoup d’autres pourraient être évités en utilisant des transitions/animations CSS au lieu d’animations JavaScript, ce qui alourdirait considérablement le temps de traitement. Je recommanderais ceci jQuery plugin pour vous permettre de tirer profit des transitions CSS, tout comme les méthodes animate().

138
kbtzr

J'ai rencontré le même problème avec le fading audio et le lecteur HTML5. Il est resté bloqué lorsque l'onglet est devenu inactif . J'ai donc découvert qu'un WebWorker était autorisé à utiliser des intervalles/délais impartis sans limitation. Je l'utilise pour poster des "ticks" sur le javascript principal.

Code 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 principal:

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;
    }
});
64
Möhre

Il existe une solution pour utiliser les travailleurs Web (comme mentionné précédemment), car ils s'exécutent dans un processus séparé et ne sont pas ralentis.

J'ai écrit un petit script qui peut être utilisé sans modifier votre code. Il remplace simplement les fonctions setTimeout, clearTimeout, setInterval, clearInterval.

Il suffit de l'inclure avant tout votre code.

plus d'infos ici

33
Ruslan Tushov

Faites juste ceci:

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

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

Les onglets de navigateur inactifs tamponnent certaines des fonctions setInterval ou setTimeout.

stop(true,true) arrêtera tous les événements mis en mémoire tampon et n'exécutera immédiatement que la dernière animation.

La méthode window.setTimeout() permet maintenant de ne pas envoyer plus d'un délai d'attente par seconde dans les onglets inactifs. De plus, il fixe désormais les délais d'attente imbriqués à la plus petite valeur autorisée par la spécification HTML5: 4 ms (au lieu des 10 ms utilisés auparavant).

13
www

Je pense que la meilleure compréhension de ce problème réside dans cet exemple: http://jsfiddle.net/TAHDb/

Je fais une chose simple ici: 

Ayez un intervalle de 1 seconde et cachez à chaque fois le premier intervalle, déplacez-le pour qu'il dure et affichez le deuxième intervalle.

Si vous restez sur la page cela fonctionne comme il est supposé. Mais si vous cachez l'onglet pendant quelques secondes, à votre retour, vous verrez une chose bizarre.

C'est comme si tous les événements qui ne se sont pas produits pendant votre temps d'inactivité se produiront tous en une fois. Ainsi, pendant quelques secondes, vous obtiendrez comme un événement X. ils sont si rapides qu'il est possible de voir les 6 plages à la fois.

Ainsi, les coutures chromées ne font que retarder les événements. Ainsi, lorsque vous récupérerez tous les événements se produiront, mais tous en même temps ...

Une application pratique était celle-ci pour un simple diaporama. Imaginez que les nombres soient des images, et si l'utilisateur reste avec l'onglet caché quand il reviendra, il verra toutes les images flotter, Totally Mesed.

Pour résoudre ce problème, utilisez stop (true, true), comme indiqué par pimvdb ..__Cela effacera la file d'attente des événements.

5
1st4ck

Pour moi, il n’est pas important de jouer de l’audio en arrière-plan comme pour d’autres ici, mon problème était que j’avais des animations et elles ont agi comme des folles quand vous étiez dans d’autres onglets et que vous reveniez à elles. Ma solution mettait ces animations dans si cela empêche onglet inactif :

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

grâce à cela, mon animation était en cours d'exécution seulement si l'onglet était actif. J'espère que cela aidera quelqu'un avec mon cas.

1
tomanolatino

Fortement influencé par la bibliothèque de Ruslan Tushov, j'ai créé ma propre petite bibliothèque . Ajoutez simplement le script dans le <head> et il corrigera setInterval et setTimeout avec ceux utilisant WebWorker.

1
Mariy

Voici ma solution approximative

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

Remarque: cette solution ne convient pas si vous aimez que votre intervalle fonctionne en arrière-plan, par exemple, lors de la lecture de fichiers audio ou ... onglet) c’est une bonne solution.

Il y a plusieurs façons d'atteindre cet objectif, peut-être que "WebWorkers" est le plus standard mais certainement, ce n'est pas le plus facile et le plus pratique, surtout si vous n'avez pas assez de temps, vous pouvez donc essayer de cette façon :

►BASIC CONCEPT:

1- Construisez un nom pour votre intervalle (ou animation) et définissez votre intervalle (animation), de sorte qu'il s'exécute lorsque l'utilisateur ouvre la page pour la première fois: _var interval_id = setInterval(your_func, 3000);

2- par $(window).focus(function() {}); et $(window).blur(function() {}); vous pouvez clearInterval(interval_id) chaque fois que le navigateur (onglet) est désactivé et réexécutez votre intervalle (animation) chaque fois que le navigateur (onglet) est réactivé par interval_id = setInterval();

►CODE D'EMPLOI:

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
ashkan nasirzadeh

setInterval et requestAnimationFrame ne fonctionnent pas lorsque l'onglet est inactif ou fonctionne mais pas aux périodes appropriées. Une solution consiste à utiliser une autre source pour les événements temporels. Par exemple, web sockets ou web workers sont deux sources d'événements qui fonctionnent correctement lorsque l'onglet est inactif. Donc, pas besoin de déplacer tout votre code vers un travailleur Web, utilisez simplement travailleur comme source d'événements de temps:

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

lien jsfiddle de cet échantillon.

0
iman

La lecture d’un fichier audio garantit aujourd’hui toutes les performances de JavaScript en arrière-plan.

Pour moi, c'était la solution la plus simple et la moins intrusive - à part jouer un son faible/presque vide, il n'y a pas d'autres effets secondaires potentiels

Vous pouvez trouver les détails ici: https://stackoverflow.com/a/51191818/914546

(D'après d'autres réponses, je vois que certaines personnes utilisent différentes propriétés de la balise Audio. Je me demande s'il est possible d'utiliser la balise Audio pour obtenir des performances optimales sans jouer quelque chose.)

0
Kaan Soral

Je pense que le moyen le plus simple d’obtenir ceci maintenant avec jQuery 3.3.x est d’utiliser cette fonction:

intervalFunction();

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

function intervalFunction() {
    // Your function hire
}

intervalFunction() est initialisé instantanément au chargement de la page, puis si votre focus est rétabli, vous essayez de réactiver l’intervalle, mais si votre page perd le focus, vous pouvez toujours y revenir.

C'est clair et facile. De plus, ces solutions n'utilisent pas les fonctions obsolètes telles que .focus(), .focusin() et .focusout()

0
kris_IV