Comment faire fonctionner setInterval quand un onglet est inactif dans Chrome?
j'ai un setInterval
qui exécute un morceau de code 30 fois par seconde. Cela fonctionne très bien, cependant quand je sélectionne un autre onglet (de sorte que l'onglet avec mon code devient inactif), le setInterval
est mis à l'é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 et puis passer à un autre onglet, attendre quelques secondes et revenir en arrière, Le l'animation continue à l'endroit où c'était lorsque vous êtes passé à l'autre onglet. Donc l'animation ne tourne pas 30 fois par seconde au cas où l'onglet serait inactif. Ceci peut être confirmé en comptant le nombre de fois que 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 conception afin d'améliorer les performances, mais y a-t-il un moyen de désactiver ce comportement? C'est en fait un inconvénient dans mon scénario.
9 réponses
sur la plupart des navigateurs, les onglets inactifs ont une faible priorité d'exécution, ce qui peut affecter les minuteries JavaScript.
si les valeurs de votre transition ont été calculées en utilisant temps réel écoulé entre les frames au lieu de incréments fixes sur chaque intervalle, vous non seulement contournez ce problème, mais vous pouvez également obtenir une autre animation en utilisant requestAnimationFrame comme il peut obtenir jusqu'à 60fps si le le processeur n'est pas très occupé.
voici un exemple JavaScript vanille 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 de Fond (non-INTERFACE utilisateur connexes)
@UpTheCreek comment:
bon pour les questions de présentation, mais encore il ya certaines choses que vous devez garder exécuter.
si vous avez des tâches de fond qui nécessite pour être exécuté avec précision à des intervalles donnés, vous pouvez utiliser HTML5 Web Workers . Jetez un oeil à 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ées par l'utilisation de CSS transitions/animations au lieu des animations basées sur JavaScript qui ajoutent une charge de travail considérable. Je vous recommande ce " jQuery plugin "que vous profitez des transitions CSS tout comme les méthodes animate()
.
j'ai rencontré le même problème avec la décoloration audio et le lecteur HTML5. Il a été rangé quand tab est devenu inactif. J'ai donc découvert qu'un WebWorker est autorisé à utiliser des intervalles/temps morts sans limitation. Je l'utilise pour poster "ticks" sur le javascript principal.
Les Web Workers 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);
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;
}
});
il y a une solution pour utiliser des Webworkers( comme mentionné ci-dessus), parce qu'ils fonctionnent dans un processus séparé et ne sont pas ralentis
j'ai écrit un petit script qui peut être utilisé sans modification de votre code - il remplace simplement les fonctions setTimeout, clearTimeout, setInterval, clearInterval.
il suffit de l'inclure avant tout votre code.
faites Simplement ceci:
var $div = $('div');
var a = 0;
setInterval(function() {
a++;
$div.stop(true,true).css("left", a);
}, 1000 / 30);
onglets de navigateur inactifs tampon certaines des fonctions setInterval
ou setTimeout
.
stop(true,true)
arrêtera tous les événements tamponnés et exécutera immédiatement seulement la dernière animation.
la méthode window.setTimeout()
clampe maintenant pour envoyer pas plus d'un temps d'arrêt par seconde dans les onglets inactifs. En outre, il fixe désormais les délais d'attente imbriqués à la plus petite valeur permise par la spécification HTML5: 4 ms (au lieu des 10 ms qu'il avait l'habitude de clamper).
je pense qu'une meilleure compréhension de ce problème est dans cet exemple: http://jsfiddle.net/TAHDb/
je suis en train de faire une chose simple ici:
ont un intervalle de 1 seconde et chaque fois masquer la première portée et le déplacer à la dernière, et montrer la deuxième portée.
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 rouée.
C'est comme tous les événements qui n'ont pas eu lieu pendant la période où vous étiez inactif maintenant aura lieu tout en 1 temps. donc, pour quelques secondes, vous obtiendrez comme X événements. ils sont si rapides qu'il est possible de voir les 6 travées en même temps.
il coutures chrome n'retards événements, de sorte que lorsque vous revenez tous les événements vont se produire, mais tous à la fois...
une application pratique étaient cette ocur iss pour un simple diaporama. Imaginez les nombres étant Images, et si l'utilisateur reste avec tab caché quand il est revenu, il verra tous les imgs flottant, totalement hypnotisé.
pour corriger cela utilisez le stop(true,true) comme pimvdb dit. Cela permettra de libérer la file d'attente d'événements.
fortement influencé par la bibliothèque de Ruslan Tushov, j'ai créé ma propre petite bibliothèque . Il suffit d'ajouter le script dans le <head>
et il patchera setInterval
et setTimeout
avec ceux qui utilisent WebWorker
.
Voici mon 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;
})();
la lecture d'un fichier audio garantit des performances Javascript de fond pour le moment
pour moi, c'était la solution la plus simple et la moins intrusive - en plus de 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'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 une performance complète, sans réellement jouer quelque chose)
j'ai pu appeler ma fonction de rappel à un minimum de 250ms en utilisant la balise audio et en gérant son événement ontimeupdate. Son appelé 3-4 fois dans une seconde. Sa meilleure qu'une seconde de retard setTimeout