Supporto dei browser
I browser moderni odierni a volte sospenderanno o eliminano del tutto le pagine quando risorse di sistema sono limitate. In futuro, i browser vorranno farlo in modo proattivo, riducendo il consumo di energia e memoria. L'API Page Lifecycle fornisce hook del ciclo di vita in modo che le pagine possano gestire in sicurezza senza influire sull'esperienza utente. Dai un'occhiata all'API per per vedere se devi implementare queste funzioni nella tua applicazione.
Sfondo
Il ciclo di vita dell'applicazione è un modo chiave gestito dai sistemi operativi moderni Google Cloud. Su Android, iOS e versioni recenti di Windows, le app possono essere avviate e in qualsiasi momento dal sistema operativo. Ciò consente a queste piattaforme di semplificare e riallocare le risorse nel momento in cui apportano maggiori vantaggi all'utente.
Sul web storicamente non esiste un ciclo di vita di questo tipo e le app possono essere mantenute. vivi a tempo indeterminato. Con un gran numero di pagine web in esecuzione, è fondamentale di risorse come memoria, CPU, batteria e rete possono superare la sottoscrizione, a un'esperienza negativa per l'utente finale.
Sebbene la piattaforma web abbia da tempo eventi correlati agli stati del ciclo di vita
ad esempio load
,
unload
e
visibilitychange
, questi eventi consentono solo agli sviluppatori
per rispondere alle modifiche
dello stato del ciclo di vita avviate dall'utente. Affinché il web funzioni
in modo affidabile sui dispositivi a basso consumo (ed essere più attenti alle risorse in generale
tutte le piattaforme) i browser necessitano di un modo per recuperare e riallocare proattivamente
Google Cloud.
Infatti, oggi i browser adottano già misure attive per risparmiare risorse per le pagine nelle schede in background e molti browser (soprattutto Chrome) vorrebbero per fare molto di più, per ridurre l'utilizzo complessivo delle risorse.
Il problema è che gli sviluppatori non hanno modo di prepararsi a questo tipo di avviati dal sistema o persino sapere che stanno accadendo. Ciò significa i browser devono essere conservativi o rischiano di compromettere le pagine web.
L'API Page Lifecycle prova a risolvere il problema:
- Presentazione e standardizzazione del concetto di stati del ciclo di vita sul web.
- La definizione di nuovi stati avviati dal sistema che consentano ai browser di limitare la risorse utilizzabili da schede nascoste o non attive.
- Creazione di nuove API ed eventi che consentono agli sviluppatori web di rispondere transizioni da e verso questi nuovi stati avviati dal sistema.
Questa soluzione offre la prevedibilità di cui gli sviluppatori web hanno bisogno per creare resilienti agli interventi del sistema e consente ai browser di ottimizzare in modo massiccio le risorse di sistema, a beneficio di tutti gli utenti web.
Il resto di questo post introdurrà le nuove funzionalità del ciclo di vita delle pagine ed esplorare come si relazionano con tutti gli stati esistenti delle piattaforme web. ed eventi. Fornirà inoltre consigli e best practice per i tipi del lavoro che gli sviluppatori dovrebbero (e non dovrebbero) svolgere in ogni stato.
Panoramica di stati ed eventi del ciclo di vita delle pagine
Tutti gli stati del ciclo di vita della pagina sono discreti e si escludono a vicenda, il che significa che una pagina può essere in un solo stato alla volta. La maggior parte delle modifiche allo stato del ciclo di vita di una pagina sono generalmente osservabili tramite eventi DOM (per le eccezioni, leggi i consigli per gli sviluppatori per ogni stato).
Forse il modo più semplice per spiegare gli stati del ciclo di vita della pagina, gli eventi che segnalano transizioni tra di loro, è rappresentato da un diagramma:
Stati
La tabella seguente spiega ogni stato in dettaglio. Elenca anche le possibili gli stati che possono precedere e dopo gli eventi, nonché gli eventi che gli sviluppatori per osservare i cambiamenti.
Stato | Descrizione |
---|---|
Attivi |
Una pagina è nello stato attiva se è visibile e presenta l'oggetto principale dell'input.
Possibili stati precedenti: |
Passiva |
Una pagina è nello stato passivo se è visibile e mostra non hanno lo stato attivo dell'input.
Possibili stati precedenti:
Possibili stati successivi: |
Nascosto |
Una pagina è nello stato nascosta se non è visibile (e non è stata congelati, eliminati o terminati).
Possibili stati precedenti:
Possibili stati successivi: |
Bloccato |
Nello stato bloccato, il browser sospende l'esecuzione di
.
congelabile
nel campo
code di attività finché la pagina non si sblocca. Ciò significa, ad esempio,
I timer JavaScript e i callback di recupero non vengono eseguiti. Già in esecuzione
attività completate (soprattutto la
I browser bloccano le pagine per preservare l'utilizzo di CPU/batteria/dati. loro come un modo per consentire in modo più rapido navigazioni avanti/indietro, evitando di dover visualizzare una pagina intera ricaricarlo.
Possibili stati precedenti:
Possibili stati successivi: |
Terminata |
Una volta che una pagina inizia a essere chiusa, vengono scaricati e cancellati dalla memoria dal browser. No le nuove attività possono iniziare in questo stato, mentre quelle in corso vengono terminati se corrono troppo a lungo.
Possibili stati precedenti:
Possibili stati successivi: |
Ignorato |
Una pagina è in stato scartato quando viene scaricata dal browser per risparmiare risorse. Nessuna attività, callback di eventi o Tutti i tipi di JavaScript possono essere eseguiti in questo stato, poiché in genere viene ignorato. avvengono sotto vincoli delle risorse, dove l'avvio di nuovi processi impossibile. Nello stato ignorato, la scheda stessa (inclusi il titolo della scheda e la favicon) è solitamente visibile all'utente anche se la pagina non è più disponibile.
Possibili stati precedenti:
Possibili stati successivi: |
Eventi
I browser inviano molti eventi, ma solo una piccola parte di essi segnala una possibile modifica allo stato del ciclo di vita della pagina. La tabella seguente illustra tutti gli eventi che riguardano il ciclo di vita ed elenca gli stati da cui possono effettuare la transizione.
Nome | Dettagli |
---|---|
focus
|
Un elemento DOM ha ricevuto lo stato attivo.
Nota: un evento
Possibili stati precedenti:
Possibili stati attuali: |
blur
|
Un elemento DOM non è più attivo.
Nota: un evento
Possibili stati precedenti:
Possibili stati attuali: |
visibilitychange
|
La proprietà
Il valore di |
freeze
*
|
La pagina è stata appena bloccata. Qualsiasi l'attività bloccabile nelle code di attività della pagina non verrà avviata.
Possibili stati precedenti:
Possibili stati attuali: |
resume
*
|
Il browser ha ripristinato una pagina bloccata.
Possibili stati precedenti:
Possibili stati attuali: |
pageshow
|
È in corso l'accesso a una cronologia delle sessioni. Può trattarsi di un caricamento di pagina completamente nuovo o di una pagina acquisita
cache back-forward. Se la pagina
è stata recuperata dalla cache back-forward, l'evento
La proprietà
Possibili stati precedenti: |
pagehide
|
È in corso l'accesso a una cronologia delle sessioni. Se l'utente accede a un'altra pagina e il browser riesce ad aggiungere
la pagina corrente indietro/avanti
per essere riutilizzato in un secondo momento, la proprietà
Possibili stati precedenti:
Possibili stati attuali: |
beforeunload
|
La finestra, il documento e le sue risorse stanno per essere scaricati. Il documento è ancora visibile e l'evento può ancora essere annullato in questo momento punto di accesso.
Importante: l'evento
Possibili stati precedenti:
Possibili stati attuali: |
unload
|
È in corso l'unload della pagina.
Avviso:
l'utilizzo dell'evento
Possibili stati precedenti:
Possibili stati attuali: |
* Indica un nuovo evento definito dall'API Page Lifecycle
Nuove funzionalità aggiunte in Chrome 68
Il grafico precedente mostra due stati avviati dal sistema anziché avviato dall'utente: bloccato e ignorato. Come accennato in precedenza, oggi i browser si bloccano e ignorano schede nascoste (a loro discrezione), ma gli sviluppatori non hanno modo di sapere quando in cui la situazione sta accadendo.
In Chrome 68, gli sviluppatori possono ora osservare quando una scheda nascosta si blocca
sbloccato ascoltando freeze
e resume
il giorno document
.
document.addEventListener('freeze', (event) => {
// The page is now frozen.
});
document.addEventListener('resume', (event) => {
// The page has been unfrozen.
});
In Chrome 68, l'oggetto document
ora include un oggetto
wasDiscarded
su Chrome desktop (l'assistenza Android è monitorata in questo problema). Per determinare se una pagina è stata eliminata in un ambiente nascosto
, puoi controllare il valore di questa proprietà al momento del caricamento della pagina (nota:
le pagine eliminate devono essere ricaricate per poter essere riutilizzate).
if (document.wasDiscarded) {
// Page was previously discarded by the browser while in a hidden tab.
}
Per consigli sulle cose importanti da fare in freeze
e resume
nonché la gestione e la preparazione per le pagine eliminate, consulta
consigli per gli sviluppatori in ogni stato.
Le prossime sezioni forniscono una panoramica di come queste nuove funzioni si inseriscono gli stati e gli eventi della piattaforma web esistente.
Come osservare gli stati del ciclo di vita della pagina nel codice
Nei campi attivi, passivi e nascosti stati, è possibile eseguire codice JavaScript che determina lo stato Stato del ciclo di vita della pagina dalle API della piattaforma web esistenti.
const getState = () => {
if (document.visibilityState === 'hidden') {
return 'hidden';
}
if (document.hasFocus()) {
return 'active';
}
return 'passive';
};
Gli stati bloccato e terminato nella
dall'altro lato, possono essere rilevati solo nel rispettivo listener di eventi
(freeze
e pagehide
) perché lo stato è
cambiare.
Come osservare i cambiamenti di stato
Basandoti sulla funzione getState()
definita in precedenza, puoi osservare tutte le metriche Pagina
Lo stato del ciclo di vita cambia con il codice seguente.
// Stores the initial state using the `getState()` function (defined above).
let state = getState();
// Accepts a next state and, if there's been a state change, logs the
// change to the console. It also updates the `state` value defined above.
const logStateChange = (nextState) => {
const prevState = state;
if (nextState !== prevState) {
console.log(`State change: ${prevState} >>> ${nextState}`);
state = nextState;
}
};
// Options used for all event listeners.
const opts = {capture: true};
// These lifecycle events can all use the same listener to observe state
// changes (they call the `getState()` function to determine the next state).
['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach((type) => {
window.addEventListener(type, () => logStateChange(getState()), opts);
});
// The next two listeners, on the other hand, can determine the next
// state from the event itself.
window.addEventListener('freeze', () => {
// In the freeze event, the next state is always frozen.
logStateChange('frozen');
}, opts);
window.addEventListener('pagehide', (event) => {
// If the event's persisted property is `true` the page is about
// to enter the back/forward cache, which is also in the frozen state.
// If the event's persisted property is not `true` the page is
// about to be unloaded.
logStateChange(event.persisted ? 'frozen' : 'terminated');
}, opts);
Questo codice fa tre cose:
- Imposta lo stato iniziale utilizzando la funzione
getState()
. - Definisce una funzione che accetta uno stato successivo e, in caso di modifica, registra le modifiche dello stato nella console.
- Aggiunge
scattare
per tutti gli eventi necessari del ciclo di vita, che a loro volta richiamano
logStateChange()
, passa allo stato successivo.
Una cosa da notare sul codice è che
tutti i listener di eventi vengono aggiunti
a window
e passano tutti
{capture: true}
Ecco alcuni dei motivi:
- Non tutti gli eventi del ciclo di vita della pagina hanno lo stesso target.
pagehide
epageshow
sono stati attivati il giornowindow
;visibilitychange
,freeze
eresume
vengono attivati il giornodocument
, mentrefocus
eblur
vengono attivati sul relativo i rispettivi elementi DOM. - La maggior parte di questi eventi non mostra le bolle, il che significa che è impossibile aggiungere listener di eventi non acquisibili in un elemento predecessore comune e osservare tutti alcune.
- La fase di cattura viene eseguita prima delle fasi target o a bolle, quindi aggiungendo aiuta ad assicurarsi che vengano eseguiti prima che altro codice possa annullarli.
Consigli per gli sviluppatori per ogni stato
In qualità di sviluppatori, è importante comprendere gli stati del ciclo di vita delle pagine e sapere come osservarle nel codice perché il tipo di lavoro che dovreste (e non) dipende in gran parte dallo stato della pagina.
Ad esempio, è chiaro che non ha senso visualizzare una notifica transitoria. all'utente se la pagina è nascosta. Sebbene questo esempio sia ovvio, ci sono altri consigli non così evidenti che vale la pena enumerazione.
Stato | Consigli per gli sviluppatori |
---|---|
Active |
Lo stato attivo è il momento più critico per l'utente e, di conseguenza, il momento più importante per far sì . in base all'input dell'utente. Dovrebbe essere ridotta la priorità di qualsiasi lavoro non UI che potrebbe bloccare il thread principale a periodi di inattività o in un worker web. |
Passive |
Nello stato passivo l'utente non interagisce con la pagina. ma può ancora vederla. Ciò significa che le animazioni e gli aggiornamenti dell'interfaccia utente senza problemi, ma la tempistica di questi aggiornamenti è meno importante. Quando la pagina passa da attiva a passiva, è sufficiente per mantenere lo stato non salvato dell'applicazione. |
Quando la pagina passa da passiva a nascosta, è possibile che l'utente non vi interagirà di nuovo finché non viene ricaricato. La transizione a hidden rappresenta spesso anche l'ultimo cambiamento di stato
che sia osservabile in modo affidabile dagli sviluppatori (questo è particolarmente vero
dispositivi mobili, in quanto gli utenti possono chiudere le schede o l'app del browser e
Ciò significa che dovresti considerare lo stato nascosto come la probabile fine del la sessione dell'utente. In altre parole, mantenere qualsiasi stato dell'applicazione non salvato. e inviare eventuali dati di analisi non inviati. Devi anche interrompere gli aggiornamenti all'interfaccia utente, dal momento che non saranno visibili dall'utente) ed è necessario interrompere tutte le attività che l'utente non vuole in esecuzione in background. |
|
Frozen |
Nello stato bloccato, . sulle attività bloccabili nella le code di attività vengono sospese fino a quando la pagina non viene sbloccata, il che potrebbe non si verificano mai (ad es. se la pagina viene eliminata). Questo significa che quando la pagina passa da nascosta a bloccata è essenziale interrompere i timer o interrompere le connessioni, se bloccato, potrebbe influire su altre schede aperte nella stessa origine o influire del browser la capacità di inserire la pagina nel Cache back/forward. In particolare, è importante che tu:
Devi inoltre mantenere qualsiasi stato di visualizzazione dinamica (ad es. posizione di scorrimento)
in una visualizzazione elenco infinita) per
Se la pagina passa da bloccata a nascosta, puoi riaprire le connessioni chiuse o riavviare i sondaggi si è arrestato quando la pagina era inizialmente bloccata. |
Terminated |
In genere non è richiesta alcuna azione da parte tua quando viene eseguita la transizione di una pagina allo stato terminato. Dato che le pagine che vengono scaricate a seguito di un'azione dell'utente, vengono sempre attraverso lo stato hidden prima di entrare nello stato terminated , lo stato nascosto è quello in cui la logica di fine sessione (ad es. lo stato dell'applicazione permanente e la generazione di report all'analisi) dovrebbe essere in esecuzione. Inoltre (come indicato nei consigli per
nascosto), è molto importante che gli sviluppatori sappiano
che la transizione allo stato terminato non possa essere
rilevati in molti casi (soprattutto sui dispositivi mobili), quindi gli sviluppatori che dipendono
sugli eventi di risoluzione (ad es. |
Discarded |
Lo stato sconosciuto non è osservabile dagli sviluppatori nel volta che una pagina viene eliminata. Questo perché le pagine solitamente sono scartate sotto i vincoli delle risorse e sbloccando una pagina solo per eseguire in risposta a un evento di annullamento non è possibile nella maggior parte dei casi. Di conseguenza, dovresti prepararti alla possibilità di uno scarto in
la modifica da hidden a bloccato, quindi potrai
reagisci al ripristino di una pagina eliminata durante il caricamento
controllo di |
Ancora una volta, poiché l'affidabilità e l'ordine degli eventi del ciclo di vita costantemente implementato in tutti i browser, il modo più semplice per seguire i consigli nella tabella è l'utilizzo PageLifecycle.js.
API del ciclo di vita legacy da evitare
Se possibile, evita i seguenti eventi.
Evento unload
Molti sviluppatori considerano l'evento unload
un callback garantito e lo usano come
un indicatore di fine sessione per salvare lo stato e inviare i dati di analisi,
è estremamente inaffidabile, soprattutto sui dispositivi mobili. L'evento unload
non
si attivano in molte situazioni tipiche di unload, inclusa la chiusura di una scheda dalla scheda
selettore su dispositivo mobile o chiudendo l'app del browser dal selettore di app.
Per questo motivo, è sempre meglio fare affidamento
visibilitychange
per determinare quando una sessione
e considera lo stato nascosto
l'ultima volta in cui puoi risparmiare dati utente e dell'app.
Inoltre, la sola presenza di un gestore di eventi unload
registrato (tramite
onunload
o addEventListener()
) può impedire ai browser di
inserire le pagine nella cache back/forward in modo da velocizzare
per i caricamenti avanti e indietro.
In tutti i browser moderni, si consiglia di utilizzare sempre
pagehide
per rilevare possibili unload di pagine (ovvero il
chiuso) anziché l'evento unload
. Se
deve supportare Internet Explorer 10 e versioni precedenti,
rilevare l'evento pagehide
e usare unload
solo se il browser non supporta
pagehide
:
const terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload';
window.addEventListener(terminationEvent, (event) => {
// Note: if the browser is able to cache the page, `event.persisted`
// is `true`, and the state is frozen rather than terminated.
});
L'evento beforeunload
L'evento beforeunload
ha un problema simile all'evento unload
, in quanto:
storicamente, la presenza di un evento beforeunload
potrebbe impedire alle pagine di
essere idonei per la cache back/forward. Browser moderni
non presentano questa restrizione. Anche se alcuni browser, per precauzione, non si attivano
l'evento beforeunload
quando tenti di inserire una pagina nel back-forward
Cache, il che significa che l'evento non è affidabile come indicatore di fine sessione.
Inoltre, alcuni browser (tra cui Chrome)
richiedi un'interazione dell'utente sulla pagina prima di consentire l'evento beforeunload
in modo che si attivi, influenzando ulteriormente la sua affidabilità.
Una differenza tra beforeunload
e unload
è che ci sono
usi legittimi di beforeunload
. Ad esempio, quando vuoi avvisare l'utente
che hanno modifiche non salvate che perderanno se continuano a scaricare la pagina.
Poiché esistono motivi validi per utilizzare beforeunload
, ti consigliamo di:
aggiungi solo listener beforeunload
quando un utente ha modifiche non salvate e poi
li rimuovi subito dopo il salvataggio.
In altre parole, non farlo (perché aggiunge un listener beforeunload
)
incondizionatamente):
addEventListener('beforeunload', (event) => {
// A function that returns `true` if the page has unsaved changes.
if (pageHasUnsavedChanges()) {
event.preventDefault();
// Legacy support for older browsers.
return (event.returnValue = true);
}
});
In questo modo, viene aggiunto il listener beforeunload
solo quando
necessario e la rimuove quando non è presente):
const beforeUnloadListener = (event) => {
event.preventDefault();
// Legacy support for older browsers.
return (event.returnValue = true);
};
// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
addEventListener('beforeunload', beforeUnloadListener);
});
// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
removeEventListener('beforeunload', beforeUnloadListener);
});
Domande frequenti
Perché non è disponibile un caricamento in corso... ?
L'API Page Lifecycle definisce gli stati come discreti e che si escludono a vicenda. Poiché una pagina può essere caricata in stato attivo, passivo o nascosto, poiché può cambiare stato (o persino essere terminato) prima che venga completato il caricamento, uno stato di caricamento separato non ha senso all'interno di questo paradigma.
La mia pagina svolge operazioni importanti quando è nascosta. Come posso evitare che venga bloccata o eliminata?
Esistono molti motivi legittimi per cui le pagine web non dovrebbero essere bloccate durante l'esecuzione nello stato nascosto. L'esempio più ovvio è un'app che riproduce musica.
Ci sono anche situazioni in cui Chrome potrebbe essere rischioso scartare una pagina,
ad esempio se contiene un modulo con input utente non inviato o se presenta
Gestore beforeunload
che avvisa quando è in corso l'unload della pagina.
Per il momento, Chrome adotta un approccio conservativo quando scarta le pagine e fallo solo quando hai la certezza che non avrà effetti sugli utenti. Ad esempio, le pagine che che è stato osservato esegue una delle seguenti azioni mentre lo stato nascosto non a meno che non si tratti di vincoli estremi per le risorse:
- Riproduzione dell'audio
- Utilizzo di WebRTC
- Aggiornamento del titolo o della favicon della tabella
- Visualizzazione degli avvisi
- Invio di notifiche push
Per le funzionalità dell'elenco corrente utilizzate per determinare se una scheda può essere sicura congelati o eliminati, consulta: Euristica per il congelamento e la Annullamento in corso... in Chrome.
Che cos'è la cache back-forward?
La cache back/forward è un termine utilizzato per descrivere un dell'ottimizzazione della navigazione implementata da alcuni browser che consente di utilizzare i pulsanti Avanti più velocemente.
Quando un utente esce da una pagina, questi browser bloccano una versione della pagina
in modo da poterla riprendere rapidamente nel caso in cui l'utente ritorni utilizzando
i pulsanti Indietro o Avanti. Ricorda che l'aggiunta di un unload
gestore di eventi impedisce la possibilità di questa ottimizzazione.
A tutti gli effetti, questo blocco è funzionalmente uguale il blocco dei browser permette di risparmiare CPU/batteria; per questo motivo sono considerati parte del ciclo di vita bloccato.
Se non posso eseguire API asincrone negli stati bloccati o terminati, come posso salvare i dati in IndexedDB?
Negli stati bloccati e terminati, attività bloccabili nelle code di attività di una pagina sono sospese, il che significa API asincrone e basate su callback come IndexedDB non possono essere utilizzati in modo affidabile.
In futuro, aggiungeremo un metodo commit()
agli oggetti IDBTransaction
, in modo da
offrono agli sviluppatori un modo per eseguire effettivamente transazioni di sola scrittura
che non richiedono callback. In altre parole, se lo sviluppatore scrive
di dati in IndexedDB e non esegue una transazione complessa costituita da letture
e scritture, il metodo commit()
potrà terminare prima che le code di attività vengano
sospeso (supponendo che il database IndexedDB sia già aperto).
Tuttavia, per il codice che deve funzionare al momento, gli sviluppatori hanno due opzioni:
- Utilizza Spazio di archiviazione di sessione: Spazio di archiviazione di sessione è sincrona ed è persistente in tutti gli scarti della pagina.
- Utilizza IndexedDB del tuo service worker: un service worker può archiviare dati in
IndexedDB dopo che la pagina è stata terminata o eliminata. Nell'
freeze
o Listener di eventipagehide
con cui puoi inviare dati al Service worker tramitepostMessage()
, e il service worker può gestire il salvataggio dei dati.
Test dell'app in stato bloccato e eliminato
Per testare il comportamento della tua app nello stato bloccato e ignorato, puoi visitare la pagina
chrome://discards
per bloccare o eliminare effettivamente i tuoi
schede aperte.
In questo modo puoi assicurarti che la tua pagina gestisca correttamente freeze
e resume
eventi e il flag document.wasDiscarded
quando le pagine vengono ricaricate dopo
uno scarto.
Riepilogo
Sviluppatori che vogliono rispettare le risorse di sistema dei dispositivi dei loro utenti devono creare le proprie app tenendo presenti gli stati del ciclo di vita delle pagine. È fondamentale che le pagine web non consumano risorse di sistema eccessive in situazioni l'utente non si aspetta
Più sviluppatori iniziano a implementare le nuove API del ciclo di vita delle pagine, più sono sicure consente ai browser di bloccare e ignorare le pagine non utilizzate. Questo significa che i browser consumeranno meno memoria, CPU, batteria e risorse di rete, il che è un vantaggio per gli utenti.