javascript - loop - setinterval mdn
¿Cómo puedo hacer que setInterval también funcione cuando una pestaña está inactiva en Chrome? (8)
Tengo un setInterval
ejecutando un fragmento de código 30 veces por segundo. Esto funciona muy bien, sin embargo, cuando selecciono otra pestaña (para que la pestaña con mi código se vuelva inactiva), setInterval
se establece en un estado inactivo por algún motivo.
Hice este caso de prueba simplificado ( http://jsfiddle.net/7f6DX/3/ ):
var $div = $(''div'');
var a = 0;
setInterval(function() {
a++;
$div.css("left", a)
}, 1000 / 30);
Si ejecuta este código y luego cambia a otra pestaña, espere unos segundos y retroceda, la animación continúa en el punto en que se encontraba cuando cambió a la otra pestaña. Por lo tanto, la animación no se ejecuta 30 veces por segundo en caso de que la pestaña esté inactiva. Esto se puede confirmar contando la cantidad de veces que se setInterval
función setInterval
cada segundo; esto no será 30, sino solo 1 o 2 si la pestaña está inactiva.
Supongo que esto se hace por diseño para mejorar el rendimiento, pero ¿hay alguna forma de desactivar este comportamiento? En realidad, es una desventaja en mi escenario.
Aquí está mi solución aproximada
(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;
})();
Creo que un mejor entendimiento sobre este problema es en este ejemplo: http://jsfiddle.net/TAHDb/
Estoy haciendo algo simple aquí:
Ten un intervalo de 1 segundo y oculta cada vez el primer tramo y muévelo al último, y muestra el segundo tramo.
Si te quedas en la página funciona como se supone. Pero si oculta la pestaña durante algunos segundos, cuando regrese verá una cosa relacionada con el correo.
Es como que todos los eventos que no ocurrieron durante el tiempo que estuviste inactivo ahora ocurrirán todo en 1 vez. entonces por unos pocos segundos obtendrás como X eventos. son tan rápidos que es posible ver los 6 tramos a la vez.
Por lo tanto, costuras de cromo solo retrasan los eventos, por lo que cuando regrese todos los eventos ocurrirán, pero todos a la vez ...
Una aplicación práctica se realizó para una presentación de diapositivas simple. Imagine que los números son Imágenes, y si el usuario se queda con la pestaña oculta cuando regrese, verá todos los imgs flotando, Totalmente mesed.
Para solucionar este problema, usa el stop (true, true) como se dice pimvdb. Esto borrará la cola del evento.
En la mayoría de los navegadores, las pestañas inactivas tienen una ejecución de baja prioridad y esto puede afectar los temporizadores de JavaScript.
Si los valores de su transición se calcularon usando tiempo real transcurrido entre cuadros en lugar de incrementos fijos en cada intervalo, no solo solucionó este problema sino que también puede lograr una animación requestAnimationFrame utilizando requestAnimationFrame ya que puede obtener hasta 60 fps si el procesador no muy ocupado.
Aquí hay un ejemplo de JavaScript vainilla de una transición de propiedad animada utilizando 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>
Para tareas en segundo plano (no relacionadas con la interfaz de usuario)
@UpTheCreek comentario:
Está bien para problemas de presentación, pero aún hay algunas cosas que necesita para seguir funcionando.
Si tiene tareas en segundo plano que deben ejecutarse con precisión en intervalos determinados, puede utilizar HTML5 Web Workers . Eche un vistazo a la respuesta de Möhre a continuación para más detalles ...
CSS vs JS "animaciones"
Este problema y muchos otros podrían evitarse mediante el uso de transiciones CSS / animaciones en lugar de animaciones basadas en JavaScript, lo que agrega una sobrecarga considerable. Recomiendo este plugin jQuery que te permite beneficiarse de las transiciones CSS al igual que los métodos animate()
.
Existe una solución para usar Web Workers (como se mencionó anteriormente), ya que se ejecutan en un proceso separado y no se ralentizan.
He escrito un pequeño script que puede usarse sin cambios en el código: simplemente anula las funciones setTimeout, clearTimeout, setInterval, clearInterval.
Simplemente inclúyelo antes de todo tu código.
Fuertemente influenciado por la biblioteca de Ruslan Tushov, he creado mi propia pequeña library . Simplemente agregue el script en <head>
y setInterval
y setTimeout
con los que usan WebWorker
.
Me encontré con el mismo problema con el desvanecimiento de audio y el reproductor HTML5. Se bloqueó cuando la pestaña se volvió inactiva. Así que descubrí que un WebWorker puede usar intervalos / tiempos de espera sin limitación. Lo uso para publicar "ticks" en el javascript principal.
Código de 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;
}
});
Pude llamar a mi función de devolución de llamada a un mínimo de 250ms usando una etiqueta de audio y manejando su evento ontimeupdate. Se llama 3-4 veces en un segundo. Es mejor que un segundo set de retrasoTimeout
Solo haz esto:
var $div = $(''div'');
var a = 0;
setInterval(function() {
a++;
$div.stop(true,true).css("left", a);
}, 1000 / 30);
Las pestañas inactivas del navegador almacenan algunas de las funciones setInterval
o setTimeout
.
stop(true,true)
detendrá todos los eventos almacenados en el búfer y ejecutará inmediatamente solo la última animación.
El método window.setTimeout()
ahora se bloquea para enviar no más de un tiempo de espera por segundo en pestañas inactivas. Además, ahora fija los tiempos de espera anidados al valor más pequeño permitido por la especificación HTML5: 4 ms (en lugar de los 10 ms que usó para fijar).