¿Cómo puedo hacer que setInterval también funcione cuando una pestaña está inactiva en Chrome?

190

Tengo un setIntervalcódigo de ejecución 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), setIntervalse establece en un estado inactivo por alguna razón.

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 regrese, la animación continúa en el punto en que estaba 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 puede confirmarse contando la cantidad de veces setIntervalque se llama a la función cada segundo; 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.

pimvdb
fuente
3
Probablemente no, a menos que lo haya pirateado junto con el Dateobjeto para ver realmente qué tiempo ha pasado.
alex
¿Podría explicar más acerca de su escenario? Tal vez no sea una desventaja.
James
2
Te refieres a este cambio de código, y lo que preguntas también se discute aquí : Oliver Mattos publicó un poco de solución, ¿tal vez también sea válido en tu caso?
Shadow Wizard es Ear For You
66
Casi siempre es mejor realizar animaciones clave sobre la cantidad de tiempo real transcurrido desde el comienzo de la animación, según se lee Date. De modo que cuando los intervalos no se disparan rápidamente (por esta u otras razones), la animación se vuelve más brusca, no más lenta.
bobince
1
El título de la pregunta me llevó aquí, sin embargo, mi caso de uso era un poco diferente: necesitaba un token de autenticación actualizado independientemente de la inactividad de una pestaña. Si eso es relevante para ti, mira mi respuesta aquí
Erez Cohen

Respuestas:

153

En la mayoría de los navegadores, las pestañas inactivas tienen una ejecución de baja prioridad y esto puede afectar a los temporizadores de JavaScript.

Si los valores de su transición se calcularon usando el tiempo real transcurrido entre fotogramas en lugar de incrementos fijos en cada intervalo, no solo soluciona este problema, sino que también puede lograr una animación más suave utilizando requestAnimationFrame, ya que puede obtener hasta 60 fps si el procesador no está muy ocupado.

Aquí hay un ejemplo de JavaScript vainilla de una transición de propiedad animada usando 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 IU)

Comentario de @UpTheCreek:

Está bien para problemas de presentación, pero aún hay algunas cosas que necesita para seguir ejecutándose.

Si usted tiene tareas en segundo plano que las necesidades a ser ejecutadas con precisión a intervalos determinados, puede utilizar Trabajadores Web HTML5 . Eche un vistazo a la respuesta de Möhre a continuación para obtener más detalles ...

CSS vs JS "animaciones"

Este problema y muchos otros podrían evitarse utilizando transiciones / animaciones CSS en lugar de animaciones basadas en JavaScript, lo que agrega una sobrecarga considerable. Recomiendo este complemento jQuery que le permite aprovechar las transiciones CSS al igual que los animate()métodos.

kbtzr
fuente
1
Probé esto en FireFox 5 y 'setInterva'l todavía se ejecuta incluso cuando la pestaña no está enfocada. Mi código en 'setInterval' desliza una presentación de diapositivas usando 'animate ()'. Parece que FireFox puso en cola la animación. Cuando vuelvo a la pestaña, FireFox abre la cola inmediatamente una tras otra, lo que da como resultado una presentación de diapositivas de rápido movimiento hasta que la cola está vacía.
nthpixel
@nthpixel sería la adición de .Stop (según la respuesta de www) ayuda en la situación tat (ya que cada vez que se despejando las animaciones anteriores)
@gordatron - .stop no ayuda a mi situación. Mismo comportamiento. Y ahora estoy en Firefox 6.0.2. Me pregunto si es la forma en que jQuery implementa .animate.
nthpixel
@cvsguimaraes: sí, buen punto. ¿Conoces algo en la especificación que garantice que los trabajadores web se ejecutarán incluso en una pestaña de fondo? Estoy un poco preocupado de que los proveedores de navegadores decidan arbitrariamente
sujetarlos
Cambiaría la última línea del código para leer before = now;para obtener el tiempo transcurrido desde el inicio en lugar del final de la llamada anterior. Esto suele ser lo que quieres al animar cosas. Tal como está ahora, si una ejecución de la función de intervalo dura 15 ms, elapsedTimedará 35 ms (en lugar de 50 ms, que es el intervalo) la próxima vez que se llame (cuando la pestaña está activa).
Jonas Berlin
71

Me encontré con el mismo problema con el desvanecimiento de audio y el reproductor HTML5. Se quedó atascado cuando la pestaña quedó inactiva. Entonces 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;
    }
});
Möhre
fuente
66
¡Ordenado! Ahora esperemos que no "arreglen" esto.
pimvdb
2
¡Excelente! ¡Este enfoque debe usarse cuando necesita exactamente temporizadores para funcionar, pero no solo para solucionar algunos problemas de animación!
Konstantin Smolyanin
1
Hice un gran metrónomo con esto. Es interesante que todas las máquinas de ritmos html5 más populares no usen este método y en su lugar usen el setTimeout regular inferior. skyl.github.io/grimoire/fretboard-prototype.html : Chrome o Safari lo juegan mejor, en este momento.
Skylar Saveland
Lo "arreglarán" si el desarrollador comienza a abusar de él. Con raras excepciones, su aplicación no es tan importante como para consumir recursos en segundo plano para proporcionar una mejora mínima en la experiencia del usuario.
Gunchars
41

Hay una solución para usar Web Workers (como se mencionó anteriormente), porque se ejecutan en un proceso separado y no se ralentizan

He escrito un pequeño script que puede usarse sin cambios en su código; simplemente anula las funciones setTimeout, clearTimeout, setInterval, clearInterval.

Solo inclúyalo antes de todo su código.

Más información aquí

Ruslan Tushov
fuente
1
Lo probé en un proyecto que incluye bibliotecas que usaban temporizadores (por lo que no pude implementar trabajadores yo mismo). Funcionó a las mil maravillas. Gracias por esta biblioteca
ArnaudR
@ ruslan-tushov acaba de probarlo en Chrome 60 pero parece que no tiene ningún efecto ... Estoy llamando a algunas funciones de varios setTimeout para agregar / eliminar elementos al DOM que están animados por animaciones CSS, y los veo congelados con cierto retraso después de la conexión pestaña (como de costumbre, sin plugins)
neoDev
@neoDev ¿Carga el HackTimer.js (o HackTimer.min.js) antes que cualquier otro JavaScript?
Davide Patti
@neoDev suena realmente extraño, porque recientemente lo intenté y funciona
Davide Patti
@DurdenP sí, también intenté ponerlo antes que cualquier otro JavaScript. ¿Tal vez porque estaba usando animaciones CSS? Recuerdo que también probé con los webworkers pero sin suerte
neoDev
13

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 setIntervalo setTimeout.

stop(true,true) detendrá todos los eventos almacenados en el búfer y ejecutará inmediatamente solo la última animación.

El window.setTimeout()método ahora se sujeta para enviar no más de un tiempo de espera por segundo en pestañas inactivas. Además, ahora sujeta 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 a los que solía sujetar).

www
fuente
1
Es bueno saberlo, por ejemplo, para actualizaciones de ajax donde los datos más recientes son siempre correctos, pero para la pregunta publicada, ¿no significaría que la animación se ralentizó / pausó significativamente ya que "a" no se habría incrementado la cantidad adecuada de veces ?
5

Creo que una mejor comprensión de este problema es en este ejemplo: http://jsfiddle.net/TAHDb/

Estoy haciendo algo simple aquí:

Tenga un intervalo de 1 segundo y cada vez oculte el primer intervalo y muévalo al último, y muestre el segundo intervalo.

Si permanece en la página, funciona como se supone. Pero si oculta la pestaña durante unos segundos, cuando regrese verá algo cansado.

Es como si todos los eventos que no ocurrieron durante el tiempo que estuvo inactivo ahora ocurran todo en 1 vez. así que por unos segundos obtendrás como X eventos. son tan rápidos que es posible ver los 6 tramos a la vez.

Por lo tanto, parece que Chrome solo retrasa los eventos, por lo que cuando regrese, todos los eventos ocurrirán, pero todos a la vez ...

Esto fue una aplicación práctica para una presentación de diapositivas simple. Imagine que los números son Imágenes, y si el usuario permanece con la pestaña oculta cuando regresó, verá todas las imágenes flotando, Totalmente malladas.

Para arreglar esto, use la parada (verdadero, verdadero) como pimvdb dijo. Esto borrará la cola del evento.

1st4ck
fuente
44
De hecho, esto se debe a requestAnimationFrameque jQuery se utiliza. Debido a algunas peculiaridades que tenía (como esta), lo eliminaron. En 1.7 no ves este comportamiento. jsfiddle.net/TAHDb/1 . Debido a que el intervalo es de una vez por segundo, esto en realidad no afecta el problema que publiqué, ya que el máximo para pestañas inactivas también es una vez por segundo, por lo que no hay diferencia.
pimvdb
4

Para mí no es importante reproducir audio en segundo plano como para otros aquí, mi problema era que tenía algunas animaciones y actuaban como locos cuando estabas en otras pestañas y volvías a ellas. Mi solución fue poner estas animaciones dentro si eso impide la pestaña inactiva :

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

gracias a eso mi animación se estaba ejecutando solo si la pestaña estaba activa. Espero que esto ayude a alguien con mi caso.

Tomaaszq
fuente
1
Lo uso así setInterval(() => !document.hidden && myfunc(), 1000)y funciona perfectamente
Didi Bear
4

Ambos setIntervaly requestAnimationFrameno funcionan cuando la pestaña está inactiva o funciona pero no en los períodos correctos. Una solución es usar otra fuente para eventos de tiempo. Por ejemplo, los sockets web o los trabajadores web son dos orígenes de eventos que funcionan bien mientras la pestaña está inactiva. Por lo tanto, no es necesario mover todo su código a un trabajador web, solo use el trabajador como fuente de eventos de tiempo:

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

Enlace jsfiddle de esta muestra.

iman
fuente
"solo use al trabajador como fuente de eventos de tiempo" Gracias por la idea
Promod Sampath Elvitigala
1

Fuertemente influenciado por la biblioteca de Ruslan Tushov, he creado mi propia biblioteca pequeña . Simplemente agregue el script en el <head>y se parcheará setIntervaly setTimeoutcon los que usan WebWorker.

Mariy
fuente
solo lo probé en Chrome 60 pero parece que no tiene ningún efecto ... Estoy llamando a algunas funciones de varios setTimeout para agregar / eliminar elementos al DOM que están animados por animaciones css, y los veo congelados con un poco de retraso después interruptor de tabulación (como de costumbre sin complementos)
neoDev
Lo uso con Chrome 60. ¿Puedes hacer una demostración y publicarla en los problemas de github?
Mariy
1

Reproducir un archivo de audio garantiza el rendimiento completo de Javascript de fondo por el momento

Para mí, fue la solución más simple y menos intrusiva: aparte de reproducir un sonido débil / casi vacío, no hay otros posibles efectos secundarios

Puede encontrar los detalles aquí: https://stackoverflow.com/a/51191818/914546

(De otras respuestas, veo que algunas personas usan diferentes propiedades de la etiqueta de audio, me pregunto si es posible usar la etiqueta de audio para un rendimiento completo, sin tener que reproducir algo)

Kaan Soral
fuente
0

Nota: esta solución no es adecuada si desea que su intervalo funcione en segundo plano, por ejemplo, reproduciendo audio o ... pero si está confundido, por ejemplo, acerca de que su animación no funciona correctamente cuando regresa a su página (pestaña), esto es Una buena solución.

Hay muchas maneras de lograr este objetivo, tal vez el "WebWorkers" es el más estándar, pero ciertamente no es el más fácil y práctico, especialmente si no tiene suficiente tiempo, así que puede intentarlo de esta manera:

► CONCEPTO BÁSICO:

1- construya un nombre para su intervalo (o animación) y configure su intervalo (animación), para que se ejecute cuando el usuario abra su página por primera vez: var interval_id = setInterval(your_func, 3000);

2- por $(window).focus(function() {});y $(window).blur(function() {});puede clearInterval(interval_id)cada vez que el navegador (pestaña) esté desactivado y vuelva a ejecutar su intervalo (animación) cada vez que el navegador (pestaña) actúe nuevamenteinterval_id = setInterval();

► CÓDIGO DE EJEMPLO:

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;
});
ashkan nasirzadeh
fuente
0

Es una pregunta bastante antigua, pero encontré el mismo problema.
Si ejecuta su web en Chrome, puede leer esta publicación Pestañas de fondo en Chrome 57 .

Básicamente, el temporizador de intervalos podría ejecutarse si no se ha agotado el presupuesto del temporizador.
El consumo de presupuesto se basa en el uso del tiempo de CPU de la tarea dentro del temporizador.
Según mi escenario, dibujo videos en el lienzo y los transporto a WebRTC.
La conexión de video webrtc seguiría actualizándose incluso si la pestaña está inactiva.

Sin embargo, tienes que usar en setIntervallugar de requestAnimationFrame.
Sin embargo, no se recomienda para la representación de la interfaz de usuario.

Sería mejor escuchar visibilityChange evento y cambiar el mechenismo en consecuencia.

Además, puedes probar @ kaan-soral y debería funcionar según la documentación.

鄭元傑
fuente
-1

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;
})();
Tengiz
fuente
El postMessageHandlerinvoca su propio evento se está manejando, teniendo así un bucle infinito que no está bloqueando la interfaz de usuario. Y mientras está en bucle infinito, está comprobando con cada gestión de eventos si hay una función de tiempo de espera o intervalo para ejecutar.
engañado
-1

Pude llamar a mi función de devolución de llamada a un mínimo de 250 ms usando la etiqueta de audio y manejando su evento ontimeupdate. Se llama 3-4 veces en un segundo. Es mejor que un segundo conjunto de retraso

usuario7356139
fuente