Il ciclo di vita di una pagina HTML è costituito da 3 importanti eventi:
DOMContentLoadedâ il browser ha completamente caricato lâHTML, e lâalbero del DOM è stato costruito, ma risorse esterne come immagini<img>e i fogli di stile potrebbero ancora non essere stati caricati.loadâ non solo lâHTML è caricato ma anche tutte le risorse esterne: immagini, fogli di stile, ecc.beforeunload/unloadâ lâutente sta lasciando la pagina.
Ogni evento potrebbe essere utile:
- evento di
DOMContentLoadedquando il DOM è pronto, quindi il gestore può scorrere i nodi del DOM, ed inizializzare lâinterfaccia. - evento di
loadquando tutte le risorse esterne sono state caricate, ed anche gli stili sono stati applicati, le dimensioni delle immagini nella pagina è nota, ecc. - evento di
beforeunloadquando lâutente sta lasciando la pagina: possiamo controllare se lâutente ha salvato le modifiche effettuate ed eventualmente chiedergli se è sicuro di voler lasciare la pagina. - evento di
unloadquando lâutente ha quasi lasciato la pagina ma possiamo ancora effettuare alcune operazioni.
Esploriamo i dettagli di questi eventi.
DOMContentLoaded
Lâevento DOMContentLoaded viene emesso dallâoggetto document.
Dobbiamo quindi utilizzare addEventListener per catturarlo:
document.addEventListener("DOMContentLoaded", ready);
// non "document.onDOMContentLoaded = ..."
Per esempio:
<script>
function ready() {
alert('DOM is ready');
// l'immagine non è stata ancora caricata (a meno che non fosse già presente in cache) quindi la dimensione è 0x0
alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
}
document.addEventListener("DOMContentLoaded", ready);
</script>
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
Nellâesempio il gestore DOMContentLoaded si aziona quando il documento è caricato, quindi può vedere tutti gli elementi del DOM, compresa <img> sotto.
Ma non aspetta che lâimmagine si carichi. Per questo alert mostra zero come dimensione.
Ad una prima occhiata lâevento DOMContentLoaded è molto semplice. Ci segnala che lâalbero del DOM è pronto, tutto qui. Ci però sono alcune peculiarità .
DOMContentLoaded e gli script
Quando il browser processa un documento HTML ed incontra un tag <script> deve eseguirlo prima di continuare a costruire il DOM. Questa è una precauzione, visto che gli script potrebbero voler modificare il DOM, e ci potrebbero anche essere dei document.write allâinterno, quindi DOMContentLoaded deve aspettare.
Quindi lâevento DOMContentLoaded avviene sicuramente dopo questi script:
<script>
document.addEventListener("DOMContentLoaded", () => {
alert("DOM pronto!");
});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script>
<script>
alert("Libreria caricata, script inline eseguito");
</script>
Nellâesempio sopra, prima vediamo âLibreria caricataâ¦â, e poi âDOM pronto!â (tutti gli script sono stati eseguiti).
Ci sono 2 eccezioni per questa regola:
- Gli script con lâattributo
async, che vedremo successivamente (info:script-async-defer), non bloccanoDOMContentLoaded. - Gli script generati dinamicamente con
document.createElement('script')e poi aggiunti alla pagina non bloccano questo evento.
DOMContentLoaded e stili
I fogli di stile esterni non influenzano il caricamento del DOM, quindi DOMContentLoaded non aspetta il loro caricamento.
Ma câè una trappola. Se abbiamo uno script dopo uno stile quello script deve aspettare affinché il foglio di stile sia stato caricato.
<link type="text/css" rel="stylesheet" href="style.css">
<script>
// lo script non viene eseguito finché il foglio di stile non è caricato
alert(getComputedStyle(document.body).marginTop);
</script>
La ragione è che lo script potrebbe voler ottenere le coordinate e altre proprietà dipendenti dallo stile degli elementi, come nellâesempio sopra. Di conseguenza lo script deve aspettare che gli stili siano caricati.
Quindi DOMContentLoaded aspetta il caricamento degli script e inoltre anche quello degli stili.
Riempimento automatico nativo del browser
Firefox, Chrome e Opera riempiono automaticamente i form durante lâevento DOMContentLoaded.
Per esempio, se la pagina contiene un form con username e password, e il browser ha memorizzato i valori, allora durante lâevento DOMContentLoaded prova a riempire automaticamente i campi (se approvato dallâutente).
Se lâevento DOMContentLoaded viene rinviato a causa dei lunghi tempi di caricamento degli script, allora anche il riempimento automatico dovrà attendere. Probabilmente avrete visto questo comportamento su alcuni siti (se utilizzate browser con riempimento automatico) â i campi della login/password non vengono riempiti immediatamente, ma câè un delay(ritardo) finché la pagina non è completamente caricata. Questo è in realtà il ritardo dovuto allâevento DOMContentLoaded.
window.onload
Lâevento load sullâoggetto window viene emesso quando tutta la pagina è stata caricata, inclusi gli stili, immagini e altre risorse.
Lâesempio sotto mostra correttamente le dimensioni dellâimmagine, perché window.onload aspetta il caricamento di tutte le immagini:
<script>
window.onload = function() { // equivale window.addEventListener('load', (event) => {
alert('Page loaded');
// l'immagine è già caricata in questo momento
alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
};
</script>
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
window.onunload
Quando un utente lascia la pagina viene emesso lâevento unload sulla window. Possiamo effettuare operazioni che non comportano un ritardo, come chiudere le finestre popup relative alla pagina.
Lâeccezione degna di nota è mandare dati analitici riguardo la pagina.
Diciamo che raccogliamo dati su come viene utilizzata la pagina: click del mouse, scroll, aree della pagina visitate e così via.
Naturalmente, lâevento unload si verifica quando lâutente ci sta lasciando e a noi vorremmo salvare i dati sul nostro server.
Esiste un metodo speciale navigator.sendBeacon(url, data) per questa necessità , descritto nei dettagli https://w3c.github.io/beacon/.
Manda i dati in background. La transizione tra una pagina e lâaltra non viene ritardata: il browser lascia la pagina ma esegue comunque sendBeacon.
Ecco come utilizzarlo:
let analyticsData = { /* oggetto con i dati raccolti */ };
window.addEventListener("unload", function() {
navigator.sendBeacon("/analytics", JSON.stringify(analyticsData));
});
- La richiesta è effettuata come POST.
- Non siamo limitati allâinvio di sole stringhe, ma sono supportati anche
Formed altri formati, come descritto nel capitolo Fetch, anche se solitamente si invia un oggetto sotto forma di stringa. - La dimensione massima dei dati è 64kb.
Quando la richiesta sendBeacon è terminata il browser probabilmente ha già lasciato il document e quindi non câè modo di ottenere la risposta del server (che comunque di solito è vuota per i dati analitici).
Câè anche un flag keepalive per effettuare richieste dopo che la pagina è stata lasciata. Potrai trovare maggiore informazioni nel capitolo API Fetch.
Se vogliamo cancellare il passaggio ad unâaltra pagina non possiamo farlo qui ma dobbiamo utilizzare un altro evento â onbeforeunload.
window.onbeforeunload
Se un utente sta cambiando pagina o sta cercando di chiudere la finestra, il gestore dellâevento beforeunload chiede una conferma aggiuntiva.
Se cancelliamo lâevento il browser potrebbe chiedere allâutente se confermano lâoperazione.
Puoi provare questo comportamento eseguendo questo codice e poi ricaricando la pagina:
window.onbeforeunload = function() {
return false;
};
Per ragioni storiche, anche tornare una stringa non vuota conta come cancellazione dellâevento. Poco tempo fa i browser lo mostravano come un messaggio ma come dicono le specifiche moderne, non dovrebbero.
Qui vediamo un esempio:
window.onbeforeunload = function() {
return "There are unsaved changes. Leave now?";
};
Il comportamento è stato cambiato perché alcuni webmaster abusavano di questo evento mostrando messaggi fastidiosi e fuorvianti. Quindi ora i vecchi browser potrebbero mostrare ancora questi messaggi, ma a parte questo â non câè modo di personalizzare il messaggio che viene mostrato allâutente.
event.preventDefault() doesnât work from a beforeunload handlerThat may sound weird, but most browsers ignore event.preventDefault().
Which means, following code may not work:
window.addEventListener("beforeunload", (event) => {
// doesn't work, so this event handler doesn't do anything
event.preventDefault();
});
Instead, in such handlers one should set event.returnValue to a string to get the result similar to the code above:
window.addEventListener("beforeunload", (event) => {
// works, same as returning from window.onbeforeunload
event.returnValue = "There are unsaved changes. Leave now?";
});
readyState
Cosa succede se impostiamo il gestore dellâevento DOMContentLoaded dopo che il documento è stato caricato?
Naturalmente, non verrà mai eseguito.
Ci sono dei casi in cui non siamo sicuri se il document è stato caricato o meno. Vorremmo che la nostra funzione venisse eseguita quando il DOM è stato caricato, sia adesso che dopo.
La proprietà document.readyState ci da informazioni a proposito dello stato di caricamento corrente.
Può assumere 3 diversi valori:
"loading"â il document si sta caricando."interactive"â il document è stato completamente letto."complete"â il document è stato completamente letto e anche tutte le risorse (come le immagini) sono state caricate.
Quindi possiamo controllare la proprietà document.readyState e in base al suo valore eseguire codice o impostare un handler.
Come questo:
function work() { /*...*/ }
if (document.readyState == 'loading') {
// sta ancora caricando, si attende l'evento
document.addEventListener('DOMContentLoaded', work);
} else {
// Il DOM è pronto
work();
}
Abbiamo a disposizione anche lâevento readystatechange che viene emesso quando lo stato cambia, quindi possiamo stampare il cambiamento di questi stati, come nellâesempio:
// stato corrente
console.log(document.readyState);
// stampa cambi di stato
document.addEventListener('readystatechange', () => console.log(document.readyState));
Lâevento readystatechange è un meccanismo alternativo per il tracciamento dello stato di caricamento della pagina ed è comparso diverso tempo fa. Al giorno dâoggi viene usato raramente.
Vediamo il flusso di tutti gli eventi per completezza.
Ecco un documento con <iframe>, <img> e handler che mostra gli eventi:
<script>
log('initial readyState:' + document.readyState);
document.addEventListener('readystatechange', () => log('readyState:' + document.readyState));
document.addEventListener('DOMContentLoaded', () => log('DOMContentLoaded'));
window.onload = () => log('window onload');
</script>
<iframe src="iframe.html" onload="log('iframe onload')"></iframe>
<img src="http://en.js.cx/clipart/train.gif" id="img">
<script>
img.onload = () => log('img onload');
</script>
Lâesempio funzionante è nella sandbox.
Lâoutput tipico:
- [1] initial readyState:loading
- [2] readyState:interactive
- [2] DOMContentLoaded
- [3] iframe onload
- [4] img onload
- [4] readyState:complete
- [4] window onload
I numeri tra le parentesi quadre indicano il tempo approssimativo di quando lâevento si verifica. Gli eventi etichettati con la stessa cifra avvengono allo stesso tempo circa (± qualche ms)
document.readyStatediventainteractiveappena prima diDOMContentLoaded. Queste 2 cose in realtà significano la stessa cosa.document.readyStatediventacompletequando tutte le risorse (iframeeimg) sono caricate. Qui possiamo vedere che avviene allâincirca nello stesso tempo diimg.onload(imgè lâultima risorsa) ewindow.onload. Passare allo statocompletesignifica lo stesso diwindow.onload. La differenza è chewindow.onloadsi attiva sempre dopo tutti gli altriloadhandler.
Riepilogo
Eventi di caricamento della pagina:
- lâevento
DOMContentLoadedscatta suldocumentquando il DOM è pronto. Possiamo quindi utilizzare Javascript sugli elementi della pagina in questa fase.- Script come
<script>...</script>o<script src="..."></script>bloccano DOMContentLoaded poiché il browser aspetta che finiscano di caricarsi per eseguirlo. - Immagini e altre risorse potrebbero prolungare ulteriormente il caricamento.
- Script come
- lâevento
loadsullawindowsi aziona quando la pagina e tutte le risorse sono caricate. Viene usato raramente perché di solito non câè bisogno di attendere per così tanto. - lâevento
beforeunloadsullawindowsi aziona quando lâutente vuole lasciare la pagina. Se cancelliamo lâevento il browser chiede allâutente se vuole veramente lasciare la pagina (per esempio se ci sono modifiche non salvate). - lâevento
unloadsullawindowsi aziona quando lâutente sta proprio lasciando la pagina, nelil gestore possiamo solo effettuare operazioni semplici che non comportano nessun ritardo o chiedere cose allâutente. Vista questa limitazione, viene usato raramente. Possiamo mandare richieste di rete connavigator.sendBeacon. document.readyStateè lo stato corrente del documento, i cambi possono essere tracciati nellâeventoreadystatechange:loadingâ il document si sta caricando.interactiveâ il document è parsato, si verifica quasi allo stesso tempo diDOMContentLoaded, ma prima di esso.complete--il document e tutte le risorse sono caricate, si verifica quasi allo stesso tempo diwindow.onload, ma prima di esso.
Commenti
<code>, per molte righe â includile nel tag<pre>, per più di 10 righe â utilizza una sandbox (plnkr, jsbin, codepenâ¦)