Como faço para o setInterval também funcionar quando uma guia está inativa no Chrome?

190

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

Fiz este caso de 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 esse código e alternar para outra guia, aguarde alguns segundos e volte, a animação continuará no ponto em que 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 setIntervalfunção é 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, a fim de melhorar o desempenho, mas existe alguma maneira de desativar esse comportamento? Na verdade, é uma desvantagem no meu cenário.

pimvdb
fonte
3
Provavelmente não, a menos que você o tenha hackeado junto com o Dateobjeto para realmente ver que horas passaram.
alex
Você poderia explicar mais sobre o seu cenário? Talvez não seja uma desvantagem.
James
2
Você quer dizer essa alteração de código, e o que você pede também é discutido aqui - Oliver Mattos postou algumas soluções, talvez seja válido no seu caso também?
Shadow Wizard é Ear For You
6
É quase sempre melhor digitar animações sobre a quantidade de tempo real decorrido desde o início da animação, conforme lido em Date. Assim, quando os intervalos não disparam rapidamente (por esse ou outros motivos), a animação fica mais instável, não mais lenta.
quer
1
O título da pergunta me levou aqui, no entanto, meu caso de uso era um pouco diferente - eu precisava de um token de autenticação atualizado, independentemente da inatividade de uma guia. Se isso é relevante para que você confira a minha resposta aqui
Erez Cohen

Respostas:

153

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

Se os valores da sua transição foram calculados usando o tempo real decorrido entre os quadros, em vez de incrementos fixos em cada intervalo, você não apenas contorna esse problema, mas também pode obter uma animação mais nítida usando requestAnimationFrame pois ele pode chegar a 60 fps se o processador não estiver muito ocupado.

Aqui está um exemplo de baunilha 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;
}
<div id="target">...HERE WE GO</div>


Para tarefas em segundo plano (não relacionadas à interface do usuário)

Comentário @UpTheCreek:

Ótimo para problemas de apresentação, mas ainda há algumas coisas que você precisa para continuar executando.

Se você tiver tarefas em segundo plano que precisem ser executadas com precisão em determinados intervalos, poderá usar HTML5 Web Workers . Dê uma olhada na resposta da Möhre abaixo para obter mais detalhes ...

CSS vs JS "animações"

Esse problema e muitos outros poderiam ser evitados usando transições / animações CSS em vez de animações baseadas em JavaScript, o que adiciona uma sobrecarga considerável. Eu recomendo este plugin jQuery que permite que você tire proveito das transições CSS, assim como os animate()métodos.

kbtzr
fonte
1
Eu tentei isso no FireFox 5 e o 'setInterva'l ainda é executado mesmo quando a guia não está em foco. Meu código em 'setInterval' mostra uma apresentação de slides usando 'animate ()'. Parece que a animação está na fila do FireFox. Quando volto à guia, o FireFox abre a fila imediatamente um após o outro, resultando em uma apresentação de slides em movimento rápido até que a fila esteja vazia.
Nthpixel 26/08
@nthpixel seria acrescentando .Stop (como por resposta de www) ajuda na situação tat (como cada vez que ele estaria limpando as animações anteriores)
@gordatron - .stop não ajuda na minha situação. Mesmo comportamento. E eu estou no FireFox 6.0.2 agora. Gostaria de saber se é assim que o jQuery implementa .animate.
Nthpixel 29/09/11
@cvsguimaraes - sim, bom ponto. Você conhece alguma coisa na especificação que garanta que os trabalhadores da Web sejam executados mesmo em uma guia em segundo plano? Estou um pouco preocupado que os fabricantes de navegadores irá decidir arbitrariamente para fixar estes também no futuro ...
UpTheCreek
Eu mudaria a próxima última linha do código para ler before = now;para obter o tempo decorrido desde o início, em vez do final da chamada anterior. Isso geralmente é o que você deseja ao animar as coisas. Como é agora, se uma execução da função de intervalo demorar 15ms, elapsedTimedará 35ms (em vez de 50ms, que é o intervalo) da próxima vez que for chamada (quando a guia estiver ativa).
Jonas Berlim
71

Corri para o mesmo problema com desbotamento de áudio e HTML5 player. Ficou preso quando a guia ficou inativa. Então, descobri que um WebWorker tem permissão para usar intervalos / tempos limite sem limitação. Eu o uso para postar "ticks" no javascript principal.

Código 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
fonte
6
Arrumado! Agora vamos torcer para que eles não "consertem" isso.
Pimvdb 27/09/12
2
Ótimo! Essa abordagem deve ser usada quando você precisa exatamente que os temporizadores estejam funcionando, mas não apenas para corrigir alguns problemas de animação!
Konstantin Smolyanin
1
Eu fiz um ótimo metrônomo com isso. Interessante que todas as máquinas de tambor html5 mais populares não usem esse método e, em vez disso, usem o setTimeout regular inferior. skyl.github.io/grimoire/fretboard-prototype.html - o chrome ou o safari são os melhores, agora.
Skylar Saveland
Eles o "consertarão" se o desenvolvedor começar a abusar. Com raras exceções, seu aplicativo não é tão importante que consome recursos em segundo plano para fornecer alguma melhoria mínima na experiência do usuário.
Gunchars 01/10/19
41

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

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

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

mais informações aqui

Ruslan Tushov
fonte
1
Eu testei em um projeto, incluindo bibliotecas que estavam usando cronômetros (para que eu não pudesse implementar os trabalhadores pessoalmente). Funcionou como um encanto. Obrigado por esta biblioteca
ArnaudR
@ ruslan-tushov acabou de experimentar no chrome 60, mas parece não ter efeito ... Estou chamando algumas funções de vários setTimeout para adicionar / remover elementos no DOM que são animados por animações em css, e os vejo congelados com algum atraso após interruptor guia (como de costume, sem plugins)
NeoDev
@neoDev Você carrega o HackTimer.js (ou HackTimer.min.js) antes de qualquer outro JavaScript?
Davide Patti
@neoDev soa realmente estranho, porque eu tentei recentemente e ele funciona
Davide Patti
@ DurdenP sim, tentei também colocá-lo antes de qualquer outro JavaScript. Talvez porque eu estava usando animações em CSS? Lembro-me também eu tentei WebWorkers mas sem sorte
NeoDev
13

Apenas faça o seguinte:

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

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

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

stop(true,true) interromperá todos os eventos em buffer e executará imediatamente apenas a última animação.

O window.setTimeout()método agora prende para enviar não mais que um tempo limite por segundo em guias inativas. Além disso, ele agora fixa os tempos limite aninhados ao menor valor permitido pela especificação HTML5: 4 ms (em vez dos 10 ms nos quais costumava fixar).

www
fonte
1
É bom saber - por exemplo, para atualizações do ajax, onde os dados mais recentes estão sempre corretos -, mas para a pergunta publicada, isso não significa que a animação foi significativamente reduzida / pausada, pois "a" não teria sido incrementado o número apropriado de vezes ?
5

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

Estou fazendo uma coisa simples aqui:

Tenha um intervalo de 1 segundo e sempre oculte o primeiro período, mova-o para o último e mostre o segundo período.

Se você permanecer na página, ele funcionará como deveria. Mas se você esconder a guia por alguns segundos, quando voltar, verá uma coisa estranha.

É como todos os eventos que não ocorreram durante o período em que você estava inativo agora ocorrerão todos de uma vez. então, por alguns segundos, você terá como X eventos. eles são tão rápidos que é possível ver todos os 6 intervalos de uma só vez.

Por isso, o costuras cromadas atrasa apenas os eventos; portanto, quando você voltar, todos os eventos ocorrerão, mas todos de uma vez ...

Uma aplicação prática ocorreu neste momento para uma apresentação de slides simples. Imagine os números como Imagens, e se o usuário ficar com a guia oculta quando ele voltar, ele verá todas as imagens flutuando, Totalmente mesed.

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

1st4ck
fonte
4
De fato, isso é devido requestAnimationFrameao jQuery usado. Por causa de algumas peculiaridades que tinha (como esta), eles a removeram. Na versão 1.7, você não vê esse comportamento. jsfiddle.net/TAHDb/1 . Como o intervalo é de uma vez por segundo, isso realmente não afeta o problema que publiquei, pois o máximo de guias inativas também é uma vez por segundo - de modo que não faz diferença.
Pimvdb
4

Para mim, não é importante reproduzir áudio em segundo plano, como acontece com outros aqui, meu problema era que eu tinha algumas animações e elas agiam como loucas quando você estava em outras abas e voltando para elas. Minha solução foi colocar essas animações dentro se isso está impedindo a guia inativa :

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

graças a isso, minha animação estava sendo executada apenas se a guia estivesse ativa. Espero que isso ajude alguém com o meu caso.

Tomaaszq
fonte
1
Eu o uso assim setInterval(() => !document.hidden && myfunc(), 1000)e funciona perfeitamente #
Didi Bear
4

Ambos setIntervale requestAnimationFramenão funcionam quando a guia está inativa ou funciona, mas não nos períodos certos. Uma solução é usar outra fonte para eventos de tempo. Por exemplo, soquetes da Web ou trabalhadores da Web são duas fontes de eventos que funcionam bem enquanto a guia está inativa. Portanto, não é necessário mover todo o seu código para um trabalhador da Web, basta usar o trabalhador como uma fonte de evento de tempo:

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

jsfiddle link desta amostra.

iman
fonte
"basta usar o trabalhador como uma fonte de eventos com horário" Obrigado pela idéia
Promod Sampath Elvitigala
1

Fortemente influenciado pela biblioteca de Ruslan Tushov, criei minha própria pequena biblioteca . Basta adicionar o script no <head>e ele será corrigido setIntervale setTimeoutcom os que forem usados WebWorker.

Mariy
fonte
tentei no chrome 60, mas parece não ter efeito ... Estou chamando poucas funções de várias setTimeout para adicionar / remover elementos do DOM que são animados por animações em css, e os vejo congelados com algum atraso após alternador de guias (como de costume, sem plug-ins)
neoDev 21/08
Uso-o com o Chrome 60. Você pode fazer uma demonstração e publicá-la nos problemas do github?
Mariy
1

A reprodução de um arquivo de áudio garante o desempenho completo do Javascript por enquanto

Para mim, era a solução mais simples e menos invasiva - além de tocar 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

(A partir de outras respostas, vejo que algumas pessoas usam propriedades diferentes da tag Audio, me pergunto se é possível usar a tag Audio para obter desempenho total, sem realmente reproduzir algo)

Kaan Soral
fonte
0

Nota: esta solução não é adequada se você gosta que seu intervalo funcione em segundo plano, por exemplo, reproduzindo áudio ou ... mas se você está confuso, por exemplo, sobre sua animação não funcionar corretamente ao retornar à sua página (guia), é uma boa solução

Existem várias maneiras de atingir esse objetivo, talvez o "WebWorkers" seja o mais padrão, mas certamente não é o mais fácil e prático, especialmente se você não tiver tempo suficiente, tente o seguinte:

► CONCEITO BÁSICO:

1- crie um nome para o seu intervalo (ou animação) e defina seu intervalo (animação), para que funcione quando o usuário abrir sua página pela primeira vez: var interval_id = setInterval(your_func, 3000);

2 por $(window).focus(function() {});e $(window).blur(function() {});você pode clearInterval(interval_id)sempre que o navegador (guia) for desativado e executar novamente o intervalo (animação) sempre que o navegador (guia) ativará novamente porinterval_id = setInterval();

►Código do exemplo:

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
fonte
0

É uma pergunta bastante antiga, mas encontrei o mesmo problema.
Se você executa sua web no chrome, pode ler esta postagem Guias de plano de fundo no Chrome 57 .

Basicamente, o cronômetro de intervalo pode ser executado se não ficar sem o orçamento do cronômetro.
O consumo do orçamento é baseado no uso do tempo da CPU da tarefa dentro do cronômetro.
Com base no meu cenário, eu desenho vídeo para tela e transporte para o WebRTC.
A conexão de vídeo webrtc continuaria atualizando até a guia está inativa.

No entanto, você deve usar em setIntervalvez de requestAnimationFrame.
embora não seja recomendado para renderização da interface do usuário.

Seria melhor ouvir visibilityChange eventos e alterar o mecanismo de acordo.

Além disso, você pode tentar o @ kaan-soral e deve funcionar com base na documentação.

鄭元傑
fonte
-1

Aqui está a 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;
})();
Tengiz
fonte
Ele postMessageHandlerchama seu próprio evento que está manipulando, com um loop infinito que não está bloqueando a interface do usuário. E enquanto faz um loop infinito, verifica com cada manipulação de evento se existe uma função de tempo limite ou intervalo a ser executada.
foobored
-1

Consegui chamar minha função de retorno de chamada no mínimo 250ms usando a tag de áudio e manipulando seu evento ontimeupdate. É chamado 3-4 vezes em um segundo. É melhor que um segundo set de atraso

user7356139
fonte