Chrome 88 (gennaio 2021) limiterà molto i timer JavaScript concatenati per le pagine nascoste in determinate condizioni. Ciò ridurrà l'utilizzo della CPU e l'utilizzo della batteria. Ci sono alcuni casi limite in cui il comportamento cambia, ma i timer vengono spesso utilizzati laddove un'API diversa sarebbe più efficiente e più affidabile.
Ok, era un termine piuttosto pesante in gergo e un po' ambiguo. Vediamo in dettaglio...
Terminologia
Pagine nascoste
In genere, nascosto significa che una scheda diversa è attiva o che la finestra è stata ridotta a icona, ma i browser potrebbero considerare una pagina nascosta ogni volta che i relativi contenuti sono completamente non visibili. Alcuni browser si spingono oltre di altri qui, ma puoi sempre usare l'API di visibilità delle pagine per monitorare quando il browser ritiene che la visibilità sia cambiata.
Timer JavaScript
Con Timer JavaScript intendo setTimeout
e setInterval
, che consentono di
pianificare una richiamata in futuro. I timer sono utili e non scompaiono, ma a volte vengono utilizzati per eseguire il polling dello stato quando un evento sarebbe più efficiente e più preciso.
Timer concatenati
Se chiami setTimeout
nella stessa attività di un callback setTimeout
, la seconda chiamata è "concatenata". Con setInterval
, ogni iterazione fa parte della catena. Questo potrebbe essere più facile da capire con il codice:
let chainCount = 0;
setInterval(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
}, 500);
E:
let chainCount = 0;
function setTimeoutChain() {
setTimeout(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
setTimeoutChain();
}, 500);
}
Come funziona la limitazione
La limitazione avviene in più fasi:
Limitazione minima
Questo accade ai timer pianificati quando si verifica una qualsiasi delle seguenti condizioni:
- La pagina è visibile.
- Nella pagina sono stati emessi rumori negli ultimi 30 secondi. L'audio può provenire da una qualsiasi delle API per la creazione di suoni, ma non viene conteggiata una traccia audio silenziosa.
Il timer non viene limitato, a meno che il timeout richiesto non sia inferiore a 4 ms e il conteggio delle catene sia pari o superiore a 5, nel qual caso il timeout è impostato su 4 ms. Non è una novità; i browser lo fanno da molti anni.
Limitazione
Questo accade ai timer pianificati quando non vengono applicate limitazioni minime e si verifica una qualsiasi delle seguenti condizioni:
- Il conteggio delle catene è inferiore a 5.
- La pagina è nascosta da meno di 5 minuti.
- WebRTC è in uso. Nello specifico, c'è un
RTCPeerConnection
conRTCDataChannel
"aperto" oMediaStreamTrack
"pubblicato".
Il browser controllerà i timer in questo gruppo una volta al secondo. Poiché vengono selezionati solo una volta al secondo, i timer con un timeout simile verranno raggruppati, consolidando il tempo necessario per l'esecuzione del codice nella scheda. Non è una novità; i browser lo fanno in una certa misura da anni.
Limitazione intensa
Ok, ecco la nuova versione di Chrome 88. Ai timer programmati viene applicata una limitazione intensiva quando non viene applicata nessuna delle condizioni di limitazione minima o limitazione e se vengono soddisfatte tutte le seguenti condizioni:
- La pagina è nascosta da più di 5 minuti.
- Il conteggio delle catene è pari o superiore a 5.
- La pagina è stata silenziosa per almeno 30 secondi.
- WebRTC non è in uso.
In questo caso, il browser controllerà i timer in questo gruppo una volta al minuto. Analogamente a prima, ciò significa che i timer verranno raggruppati in questi controlli minuto per minuto.
Soluzioni alternative
Di solito esiste un'alternativa migliore a un timer, oppure i timer possono essere combinati con qualcos'altro per rispettare le CPU e la durata della batteria.
Sondaggi statali
Si tratta dell'uso più comune (uso improprio) dei timer, che vengono utilizzati per effettuare controlli continui o per eseguire sondaggi per vedere se qualcosa è cambiato. Nella maggior parte dei casi esiste un'opzione equivalente per il formato push, in cui l'elemento ti informa della modifica quando si verifica, senza dover continuare a controllare. Verifica se esiste un evento che raggiunge lo stesso obiettivo.
Ecco alcuni esempi:
- Se hai bisogno di sapere quando un elemento entra nell'area visibile, utilizza
IntersectionObserver
. - Se devi sapere quando un elemento cambia dimensione, utilizza
ResizeObserver
. - Se devi sapere quando il DOM cambia, utilizza
MutationObserver
o magari callback del ciclo di vita degli elementi personalizzati. - Anziché eseguire il polling di un server, prendi in considerazione i web socket, gli eventi inviati dal server, i messaggi push o il recupero dei flussi.
- Se devi reagire ai cambiamenti dello stage nell'audio/video, utilizza eventi come
timeupdate
eended
oppurerequestVideoFrameCallback
se devi fare qualcosa con ogni frame.
Esistono anche degli attivatori di notifiche se vuoi mostrare una notifica in un momento specifico.
Animazione
L'animazione è un elemento visivo, quindi non dovrebbe utilizzare il tempo di CPU quando la pagina è nascosta.
requestAnimationFrame
è molto più utile nella pianificazione dell'animazione rispetto ai timer JavaScript. Si sincronizza con la frequenza di aggiornamento del dispositivo, assicurandoti di ricevere un solo callback per frame visualizzabile e il tempo massimo per creare quel frame. Inoltre, requestAnimationFrame
attenderà che la pagina sia visibile, quindi non utilizza alcuna CPU quando la pagina è nascosta.
Se puoi dichiarare l'intera animazione in anticipo, valuta l'utilizzo di animazioni CSS o
dell'API per le animazioni web. Queste soluzioni
presentano gli stessi vantaggi di requestAnimationFrame
, ma il browser può eseguire
ottimizzazioni aggiuntive, come la composizione automatica, e sono generalmente
più semplici da utilizzare.
Se l'animazione ha una frequenza fotogrammi bassa (ad esempio un cursore lampeggiante), i timer sono ancora
l'opzione migliore in questo momento, ma puoi combinarli con requestAnimationFrame
per ottenere il meglio da entrambe le modalità:
function animationInterval(ms, signal, callback) {
const start = document.timeline.currentTime;
function frame(time) {
if (signal.aborted) return;
callback(time);
scheduleFrame(time);
}
function scheduleFrame(time) {
const elapsed = time - start;
const roundedElapsed = Math.round(elapsed / ms) * ms;
const targetNext = start + roundedElapsed + ms;
const delay = targetNext - performance.now();
setTimeout(() => requestAnimationFrame(frame), delay);
}
scheduleFrame(start);
}
Uso:
const controller = new AbortController();
// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
console.log('tick!', time);
});
// And stop it:
controller.abort();
Test
Questo cambiamento verrà attivato per tutti gli utenti in Chrome 88 (gennaio 2021). Attualmente è attivo per il 50% degli utenti di Chrome Beta, Dev e Canary. Se vuoi testarlo, utilizza questo flag della riga di comando quando avvii Chrome Beta, Dev o Canary:
--enable-features="IntensiveWakeUpThrottling:grace_period_seconds/10,OptOutZeroTimeoutTimersFromThrottling,AllowAggressiveThrottlingWithWebSocket"
L'argomento grace_period_seconds/10
determina un'attivazione intensiva della limitazione dopo 10 secondi di occultamento della pagina, anziché l'intera durata di 5 minuti, rendendo più facile vedere l'impatto della limitazione.
Il futuro
Poiché i timer sono una fonte di utilizzo eccessivo della CPU, continueremo a cercare modi per limitarli senza danneggiare i contenuti web e che possiamo aggiungere/modificare per soddisfare i casi d'uso. Personalmente, vorrei eliminare la necessità di animationInterval
in favore di callback di animazione a bassa frequenza efficienti. In caso di domande, contattaci su Twitter.
Foto dell'intestazione di Heather Zabriskie su Unsplash.