Come posso fare in modo che setInterval funzioni anche quando una scheda non è attiva in Chrome?


190

Ho setIntervalun pezzo di codice in esecuzione 30 volte al secondo. Funziona benissimo, tuttavia quando seleziono un'altra scheda (in modo che la scheda con il mio codice diventi inattiva), setIntervalper qualche motivo è impostata su uno stato inattivo.

Ho realizzato questo caso di test semplificato ( http://jsfiddle.net/7f6DX/3/ ):

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

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

Se esegui questo codice e poi passi a un'altra scheda, attendi qualche secondo e torni indietro, l'animazione continua nel punto in cui era quando sei passato all'altra scheda. Quindi l'animazione non viene eseguita 30 volte al secondo nel caso in cui la scheda sia inattiva. Ciò può essere confermato contando il numero di volte in cui la setIntervalfunzione viene chiamata ogni secondo - questo non sarà 30 ma solo 1 o 2 se la scheda è inattiva.

Immagino che ciò avvenga in base alla progettazione in modo da migliorare le prestazioni, ma esiste un modo per disabilitare questo comportamento? In realtà è uno svantaggio nel mio scenario.


3
Probabilmente no, a meno che tu non l'abbia hackerato insieme Dateall'oggetto per vedere davvero a che ora è passato.
alex

Potresti spiegare di più sul tuo scenario? Forse non è un tale svantaggio.
James,

2
Intendi questo cambio di codice, e anche quello che chiedi è discusso qui - Oliver Mattos ha pubblicato un po 'di soluzione, forse è valido anche nel tuo caso?
Shadow Wizard è Ear For You

6
È quasi sempre meglio animare le chiavi sulla quantità di tempo reale trascorso dall'inizio dell'animazione, come letto Date. In modo che quando gli intervalli non si attivano rapidamente (per questo o altri motivi) l'animazione diventa solo più nervosa, non più lenta.
Bobince

1
Il titolo della domanda mi ha portato qui, tuttavia il mio caso d'uso era un po 'diverso: avevo bisogno di un token di autenticazione aggiornato indipendentemente dall'inattività di una scheda. Se questo è rilevante per te, controlla la mia risposta qui
Erez Cohen,

Risposte:


153

Sulla maggior parte dei browser le schede inattive hanno un'esecuzione a bassa priorità e questo può influire sui timer JavaScript.

Se i valori della transizione sono stati calcolati utilizzando il tempo reale trascorso tra i fotogrammi anziché gli incrementi fissi su ciascun intervallo, non solo risolvete questo problema, ma potete anche ottenere un'animazione più soffice utilizzando requestAnimationFrame in quanto può ottenere fino a 60fps se il processore non lo è molto occupato.

Ecco un esempio JavaScript di vaniglia di una transizione di proprietà animata utilizzando 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>


Per attività in background (non correlate all'interfaccia utente)

@UpTheCreek comment:

Va bene per problemi di presentazione, ma ci sono ancora alcune cose che è necessario continuare a correre.

Se hai attività in background che devono essere eseguite con precisione a determinati intervalli, puoi utilizzare HTML5 Web Workers . Dai un'occhiata alla risposta di Möhre qui sotto per maggiori dettagli ...

"Animazioni" CSS vs JS

Questo problema e molti altri potrebbero essere evitati utilizzando transizioni / animazioni CSS anziché animazioni basate su JavaScript che aggiungono un notevole sovraccarico. Consiglierei questo plugin jQuery che ti consente di trarre vantaggio dalle transizioni CSS proprio come i animate()metodi.


1
Ho provato questo su FireFox 5 e 'setInterva'l funziona ancora anche quando la scheda non è attiva. Il mio codice in "setInterval" fa scorrere uno slide usando "animate ()". Sembra che l'animazione sia messa in coda da FireFox. Quando torno sulla scheda, FireFox fa scoppiare la coda immediatamente una dopo l'altra risultando in una presentazione in rapido movimento fino a quando la coda è vuota.
nthpixel,

@nthpixel aggiungerebbe .stop (secondo la risposta di www) aiuto nella situazione tat (come ogni volta che

@gordatron - .stop non aiuta la mia situazione. Stesso comportamento. E ora sono su FireFox 6.0.2. Mi chiedo se sia il modo in cui jQuery implementa .animate.
nthpixel,

@cvsguimaraes - sì buon punto. Sei a conoscenza di qualcosa nelle specifiche che garantisca l'esecuzione dei web worker anche quando si trova in una scheda in background? Sono un po 'preoccupato che i venditori di browser decideranno arbitrariamente di bloccare anche questi in futuro ...
UpTheCreek

Vorrei modificare l'ultima riga del codice in modo da leggere before = now;per ottenere il tempo trascorso dall'inizio anziché la fine della chiamata precedente. Questo è di solito quello che vuoi quando animi le cose. Come è ora, se un'esecuzione della funzione intervallo impiega 15 ms, elapsedTimedarà 35ms (invece di 50ms che è l'intervallo) la prossima volta che viene chiamato (quando la scheda è attiva).
Jonas Berlin,

71

Ho riscontrato lo stesso problema con dissolvenza audio e lettore HTML5. Si è bloccato quando la scheda è diventata inattiva. Quindi ho scoperto che un WebWorker è autorizzato a utilizzare intervalli / timeout senza limitazione. Lo uso per pubblicare "tick" sul javascript principale.

Codice 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 principale:

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

6
! Neat Ora speriamo che non "risolvano" questo.
pimvdb,

2
Grande! Questo approccio dovrebbe essere usato quando hai bisogno esattamente dei timer per funzionare ma non solo per risolvere alcuni problemi di animazione!
Konstantin Smolyanin,

1
Ho fatto un grande metronomo con questo. È interessante notare che tutte le drum machine html5 più popolari non usano questo metodo e usano invece il setTimeout normale inferiore. skyl.github.io/grimoire/fretboard-prototype.html - Chrome o Safari gioca al meglio, proprio ora.
Skylar Saveland,

Lo "ripareranno" se lo sviluppatore inizia ad abusarlo. Con rare eccezioni, la tua app non è così importante che dovrebbe consumare risorse in background per fornire un miglioramento minimo dell'esperienza utente.
Gunchars

41

Esiste una soluzione per utilizzare i Web Worker (come menzionato in precedenza), poiché vengono eseguiti in processi separati e non vengono rallentati

Ho scritto un piccolo script che può essere utilizzato senza modifiche al codice: sostituisce semplicemente le funzioni setTimeout, clearTimeout, setInterval, clearInterval.

Basta includerlo prima di tutto il codice.

maggiori informazioni qui


1
L'ho testato su un progetto che includeva librerie che utilizzavano timer (quindi non potevo implementare i lavoratori da solo). Ha funzionato come un fascino. Grazie per questa biblioteca
ArnaudR

@ ruslan-tushov l'ho appena provato su Chrome 60 ma sembra non avere alcun effetto ... Sto chiamando poche funzioni da diversi setTimeout per aggiungere / rimuovere elementi al DOM che sono animati da animazioni CSS e li vedo congelati con un certo ritardo dopo il cambio di tabulazione (come al solito senza plugin)
neoDev

@neoDev Carichi HackTimer.js (o HackTimer.min.js) prima di qualsiasi altro JavaScript?
Davide Patti,

@neoDev suona davvero strano, perché ci ho provato di recente e funziona
Davide Patti l'

@DurdenP sì, ho provato anche a metterlo prima di qualsiasi altro JavaScript. Forse perché stavo usando le animazioni CSS? Ricordo anche di aver provato i webworker ma senza fortuna
neoDev

13

Basta fare questo:

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

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

Le schede del browser inattive memorizzano in buffer alcune delle funzioni setIntervalo setTimeout.

stop(true,true) interromperà tutti gli eventi bufferizzati ed eseguirà immediatamente solo l'ultima animazione.

Il window.setTimeout()metodo ora blocca per inviare non più di un timeout al secondo nelle schede inattive. Inoltre, ora blocca i timeout nidificati al valore più piccolo consentito dalla specifica HTML5: 4 ms (anziché i 10 ms utilizzati per il serraggio).


1
Questo è buono a sapersi - ad esempio per gli aggiornamenti ajax in cui gli ultimi dati sono sempre corretti - ma per la domanda posta non significherebbe che l'animazione è stata significativamente rallentata / messa in pausa poiché "a" non sarebbe stata incrementata il numero appropriato di volte ?

5

Penso che una migliore comprensione di questo problema sia in questo esempio: http://jsfiddle.net/TAHDb/

Sto facendo una cosa semplice qui:

Avere un intervallo di 1 secondo e ogni volta nascondere il primo intervallo e spostarlo per ultimo e mostrare il secondo intervallo.

Se rimani sulla pagina funziona come dovrebbe. Ma se nascondi la scheda per alcuni secondi, quando torni vedrai una cosa bizzarra.

È come tutti gli eventi che non sono avvenuti durante il periodo in cui sei stato inattivo ora si verificheranno tutti in 1 volta. quindi per alcuni secondi otterrai X eventi. sono così veloci che è possibile vedere tutte e 6 le campate contemporaneamente.

Quindi cuciture Chrome ritarda solo gli eventi, quindi quando torni tutti gli eventi si verificano ma tutti in una volta ...

Un'applicazione pratica è stata questa ocur iss per una semplice presentazione. Immagina che i numeri siano Immagini, e se l'utente rimane con la linguetta nascosta quando torna, vedrà tutti gli img fluttuare, Totally mesed.

Per risolvere questo problema, usa l'arresto (vero, vero) come ha detto pimvdb. Ciò cancellerà la coda degli eventi.


4
In realtà questo è dovuto a requestAnimationFramequel jQuery utilizzato. A causa di alcune stranezze (come questa), l'hanno rimosso. In 1.7 non vedi questo comportamento. jsfiddle.net/TAHDb/1 . Poiché l'intervallo è una volta al secondo, ciò in realtà non influisce sul problema che ho pubblicato, poiché il massimo per le schede inattive è anche una volta al secondo, quindi non fa differenza.
pimvdb,

4

Per me non è importante riprodurre l'audio in background come per gli altri qui, il mio problema era che avevo alcune animazioni e si comportavano da matti quando eri in altre schede e tornavi da loro. La mia soluzione è stata quella di inserire queste animazioni se ciò impedisce la scheda inattiva :

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

grazie a ciò la mia animazione era in esecuzione solo se la scheda era attiva. Spero che questo possa aiutare qualcuno con il mio caso.


1
Lo uso in questo modo setInterval(() => !document.hidden && myfunc(), 1000)e funziona perfettamente
Didi Bear

4

Entrambi setIntervale requestAnimationFramenon funzionano quando la scheda è inattiva o funziona ma non nei periodi giusti. Una soluzione è utilizzare un'altra fonte per eventi temporali. Ad esempio i web socket o i web worker sono due fonti di eventi che funzionano correttamente mentre la scheda è inattiva. Quindi non è necessario spostare tutto il codice su un web worker, basta usare worker come fonte di eventi temporali:

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

collegamento jsfiddle di questo esempio.


"usa solo il lavoratore come fonte di eventi temporali" Grazie per l'idea
Promod Sampath Elvitigala,

1

Fortemente influenzato dalla biblioteca di Ruslan Tushov, ho creato la mia piccola biblioteca . Basta aggiungere lo script in <head>e verrà corretto setIntervale setTimeoutcon quelli che usano WebWorker.


ho appena provato su Chrome 60 ma sembra non avere alcun effetto ... Sto chiamando poche funzioni da diversi setTimeout per aggiungere / rimuovere elementi al DOM che sono animati da animazioni CSS e li vedo congelati con un certo ritardo dopo tab switch (come al solito senza plugin)
neoDev

Lo uso con Chrome 60. Puoi creare una demo e pubblicarla sui problemi di github?
Mariy,

1

La riproduzione di un file audio garantisce per il momento prestazioni JavaScript di sfondo completo

Per me, è stata la soluzione più semplice e meno invadente - oltre a riprodurre un suono debole / quasi vuoto, non ci sono altri potenziali effetti collaterali

Puoi trovare i dettagli qui: https://stackoverflow.com/a/51191818/914546

(Da altre risposte, vedo che alcune persone usano proprietà diverse del tag Audio, mi chiedo se sia possibile utilizzare il tag Audio per le massime prestazioni, senza effettivamente suonare qualcosa)


0

Nota: questa soluzione non è adatta se ti piace che l'intervallo funzioni in background, ad esempio la riproduzione di audio o ... ma se sei confuso, ad esempio, se l'animazione non funziona correttamente quando torni alla tua pagina (scheda), questo è una buona soluzione.

Esistono molti modi per raggiungere questo obiettivo, forse il "WebWorkers" è il più standard ma sicuramente non è il più semplice e utile, soprattutto se non hai abbastanza tempo, quindi puoi provare in questo modo:

► CONCETTO BASE:

1- crea un nome per il tuo intervallo (o animazione) e imposta il tuo intervallo (animazione), in modo che venga eseguito quando l'utente apre per la prima volta la tua pagina: var interval_id = setInterval(your_func, 3000);

2- by $(window).focus(function() {});e $(window).blur(function() {});puoi clearInterval(interval_id)ogni volta che il browser (scheda) viene disattivato e Riesegui l'intervallo (animazione) ogni volta che il browser (scheda) riattivainterval_id = setInterval();

► CODICE CAMPIONE:

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

0

È una domanda piuttosto vecchia ma ho riscontrato lo stesso problema.
Se esegui il tuo Web su Chrome, puoi leggere questo post Schede di sfondo in Chrome 57 .

Fondamentalmente il timer intervallo potrebbe essere eseguito se non ha esaurito il budget del timer.
Il consumo di budget si basa sull'utilizzo del tempo della CPU dell'attività all'interno del timer.
Sulla base del mio scenario, disegno video su tela e trasporto su WebRTC.
La connessione video webrtc continuerebbe ad aggiornarsi anche se la scheda è inattiva.

Tuttavia devi usare setIntervalinvece di requestAnimationFrame.
tuttavia non è consigliato per il rendering dell'interfaccia utente.

Sarebbe meglio ascoltare visibilityChange evento e cambiare di conseguenza il mechenismo.

Inoltre, potresti provare @ kaan-soral e dovrebbe funzionare in base alla documentazione.


-1

Ecco la mia soluzione approssimativa

(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;
})();

Il postMessageHandlerinvoca il proprio evento sta gestendo, così da avere un ciclo infinito che non blocca l'interfaccia utente. E mentre è in loop continuo, controlla con ogni gestione degli eventi se c'è una funzione di timeout o intervallo da eseguire.
Foobored

-1

Sono stato in grado di chiamare la mia funzione di richiamata a un minimo di 250 ms utilizzando il tag audio e gestendo l'evento ontimeupdate. Si chiama 3-4 volte in un secondo. È meglio di un secondo in ritardo setTimeout

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.