Como posso fazer o setInterval funcionar quando uma guia está inativa no Chrome?

Eu tenho um setInterval executando um pedaço de código 30 vezes por segundo. Isso funciona muito bem, no entanto, quando eu seleciono outra guia (para que a guia com meu código fique inativa), o setInterval é definido para um estado inativo por algum motivo.

Eu fiz este teste simplificado ( http://jsfiddle.net/7f6DX/3/ ):

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

Se você executar este código e, em seguida, mudar para outra guia, aguarde alguns segundos e volte, a animação continua no ponto em que estava quando você mudou para a outra guia. Portanto, a animação não está sendo executada 30 vezes por segundo, caso a guia esteja inativa. Isso pode ser confirmado contando a quantidade de vezes que a function setInterval é chamada a cada segundo – isso não será 30, mas apenas 1 ou 2 se a guia estiver inativa.

Eu acho que isso é feito por design, de modo a melhorar o desempenho, mas existe alguma maneira de desativar esse comportamento? Na verdade, é uma desvantagem no meu cenário.

Na maioria dos navegadores, as guias inativas têm baixa prioridade de execução e isso pode afetar os timers do JavaScript.

Se os valores de sua transição foram calculados usando o tempo real entre frames em vez de incrementos fixos em cada intervalo, você não apenas resolve esse problema, mas também pode obter uma animação sufocante usando requestAnimationFrame, já que pode chegar a 60fps se o processador não for muito ocupado.

Aqui está um exemplo de vanilla JavaScript de uma transição de propriedade 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; } 
 
...HERE WE GO

Eu corri para o mesmo problema com o desvanecimento de áudio e o player HTML5. Ficou preso quando o separador se tornou inativo. Então eu descobri que um WebWorker tem permissão para usar intervalos / timeouts sem limitação. Eu uso para postar “ticks” para o javascript principal.

Código do 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; } }); 

Há uma solução para usar Web Workers (como mencionado anteriormente), porque eles são executados em processo separado e não são retardados

Eu escrevi um pequeno script que pode ser usado sem alterações no seu código – ele simplesmente sobrescreve as funções setTimeout, clearTimeout, setInterval, clearInterval.

Basta incluí-lo antes de todo o seu código.

mais informações aqui

Apenas faça isso:

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

As guias do navegador inativas armazenam em buffer algumas das funções setInterval ou setTimeout .

stop(true,true) interrompe todos os events armazenados em buffer e executa imediatamente apenas a última animação.

O método window.setTimeout() agora se ajusta para enviar mais de um tempo limite por segundo em abas inativas. Além disso, agora fixa tempos limite nesteds ao menor valor permitido pela especificação HTML5: 4 ms (em vez dos 10 ms usados ​​para clampear).

Eu acho que um melhor entendimento sobre esse problema está neste exemplo: http://jsfiddle.net/TAHDb/

Eu estou fazendo uma coisa simples aqui:

Ter um intervalo de 1 seg e cada vez ocultar o primeiro span e movê-lo para durar e mostrar o segundo span.

Se você ficar na página, funciona como deveria. Mas se você esconder a aba por alguns segundos, quando voltar, verá uma coisa estranha.

É como se todos os events que não ocorreram durante o tempo que você estava inativo agora ocorram todos em 1 vez. Então, por alguns segundos você terá como events X. eles são tão rápidos que é possível ver todos os 6 espaços ao mesmo tempo.

Por isso, o cromado das costuras só atrasa os events, por isso, quando regressar, todos os events ocorrerão, mas de uma só vez …

Uma aplicação prática foi esta ocur iss para uma simples apresentação de slides. Imagine os números sendo Imagens, e se o usuário ficar com a aba escondida quando ele voltar, verá todos os imgs flutuando, Totalmente mesed.

Para consertar isso, use o stop (true, true) como pimvdb disse. Isso limpará a fila de events.

Fortemente influenciada pela biblioteca de Ruslan Tushov, criei minha própria pequena biblioteca . Basta adicionar o script no e ele corrigirá setInterval e setTimeout com aqueles que usam o WebWorker .

Aqui está minha solução 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; })(); 

A reprodução de um arquivo de áudio garante o desempenho total do Javascript em segundo plano por enquanto

Para mim, foi a solução mais simples e menos intrusiva – além de reproduzir um som fraco / quase vazio, não há outros efeitos colaterais em potencial

Você pode encontrar os detalhes aqui: https://stackoverflow.com/a/51191818/914546

(De outras respostas, vejo que algumas pessoas usam propriedades diferentes da tag de áudio, pergunto-me se é possível usar a tag de áudio para o desempenho total, sem realmente tocar algo)

Consegui chamar minha function de retorno de chamada no mínimo de 250 ms usando a tag de áudio e manipulando seu evento ontimeupdate. É chamado 3-4 vezes em um segundo. Seu melhor que um segundo atrasado setTimeout