El ciclo de vida de una página HTML tiene tres eventos importantes:
DOMContentLoaded
– el navegador HTML está completamente cargado y el árbol DOM está construido, pero es posible que los recursos externos como<img>
y hojas de estilo aún no se hayan cargado.load
– no solo se cargó el HTML, sino también todos los recursos externos: imágenes, estilos, etc.beforeunload/unload
– el usuario sale de la pagina.
Cada evento puede ser útil:
- Evento
DOMContentLoaded
– DOM está listo, por lo que el controlador puede buscar nodos DOM, inicializar la interfaz. - Evento
load
– se cargan recursos externos, por lo que se aplican estilos, se conocen tamaños de imagen, etc. - Evento
beforeunload
– el usuario se va: podemos comprobar si el usuario guardó los cambios y preguntarle si realmente quiere irse. - Evento
unload
– el usuario casi se fue, pero aún podemos iniciar algunas operaciones, como enviar estadísticas.
Exploremos los detalles de estos eventos.
DOMContentLoaded
El evento DOMContentLoaded
ocurre en el objeto document
.
Debemos usar addEventListener
para capturarlo:
document.addEventListener("DOMContentLoaded", ready);
// no "document.onDOMContentLoaded = ..."
Por ejemplo:
<script>
function ready() {
alert('DOM is ready');
// la imagen aún no está cargada (a menos que se haya almacenado en caché), por lo que el tamaño es 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">
En el ejemplo, el controlador del evento DOMContentLoaded
se ejecuta cuando el documento está cargado, por lo que puede ver todos los elementos, incluido el <img>
que está después de él.
Pero no espera a que se cargue la imagen. Entonces, alert
muestra los tamaños en cero.
A primera vista, el evento DOMContentLoaded
es muy simple. El árbol DOM está listo – aquí está el evento. Sin embargo, hay algunas peculiaridades.
DOMContentLoaded y scripts
Cuando el navegador procesa un documento HTML y se encuentra con una etiqueta <script>
, debe ejecutarla antes de continuar construyendo el DOM. Esa es una precaución, ya que los scripts pueden querer modificar el DOM, e incluso hacer document.write
en él, por lo queDOMContentLoaded
tiene que esperar.
Entonces DOMContentLoaded siempre ocurre después de tales scripts:
<script>
document.addEventListener("DOMContentLoaded", () => {
alert("DOM listo!");
});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script>
<script>
alert("Libreria cargada, linea de script ejecutada");
</script>
En el ejemplo anterior, primero vemos “Biblioteca cargada …” y luego “¡DOM listo!” (se ejecutan todos los scripts).
Hay dos excepciones a esta regla:
- Scripts con el atributo
async
, que cubriremos un poco más tarde, no bloquea elDOMContentLoaded
. - Los scripts que se generan dinámicamente con
document.createElement('script')
y luego se agregan a la página web, tampoco bloquean este evento.
DOMContentLoaded y estilos
Las hojas de estilo externas no afectan a DOM, por lo que DOMContentLoaded
no las espera.
Pero hay una trampa. Si tenemos un script después del estilo, entonces ese script debe esperar hasta que se cargue la hoja de estilo:
<link type="text/css" rel="stylesheet" href="style.css">
<script>
// el script no se ejecuta hasta que se cargue la hoja de estilo
alert(getComputedStyle(document.body).marginTop);
</script>
La razón de esto es que el script puede querer obtener coordenadas y otras propiedades de elementos dependientes del estilo, como en el ejemplo anterior. Naturalmente, tiene que esperar a que se carguen los estilos.
Como DOMContentLoaded espera los scripts, ahora también espera a los estilos que están antes que ellos.
Autocompletar del navegador integrado
Firefox, Chrome y Opera autocompletan formularios en DOMContentLoaded
.
Por ejemplo, si la página tiene un formulario con nombre de usuario y contraseña, y el navegador recuerda los valores, entonces en DOMContentLoaded
puede intentar completarlos automáticamente (si el usuario lo aprueba).
Entonces, si DOMContentLoaded
es pospuesto por scripts de largo tiempo de carga, el autocompletado también espera. Probablemente haya visto eso en algunos sitios (si usa la función de autocompletar del navegador): los campos de inicio de sesión/contraseña no se autocompletan inmediatamente, sino con retraso hasta que la página se carga por completo. En realidad es el retraso hasta el evento DOMContentLoaded
.
window.onload
El evento load
en el objeto window
se activa cuando se carga toda la página, incluidos estilos, imágenes y otros recursos. Este evento está disponible a través de la propiedad onload
.
El siguiente ejemplo muestra correctamente los tamaños de las imágenes, porque window.onload
espera todas las imágenes:
<script>
window.onload = function() { // también puede usar window.addEventListener('load', (event) => {
alert('Página cargada');
// la imagen es cargada al mismo tiempo
alert(`Tamaño de imagen: ${img.offsetWidth}x${img.offsetHeight}`);
};
</script>
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
window.onunload
Cuando un visitante abandona la página, el evento unload
se activa en window
. Podemos hacer algo allí que no implique un retraso, como cerrar ventanas emergentes relacionadas.
La excepción notable es el envío de análisis.
Supongamos que recopilamos datos sobre cómo se usa la página: clicks del mouse, desplazamientos, áreas de página visitadas, etc.
Naturalmente, el evento unload
sucede cuando el usuario nos deja y nos gustaría guardar los datos en nuestro servidor.
Existe un método especial navigator.sendBeacon(url, data)
para tales necesidades, descrito en la especificación https://w3c.github.io/beacon/.
Este envía los datos en segundo plano. La transición a otra página no se retrasa: el navegador abandona la página, pero aún realiza sendBeacon
.
Así es como se usa:
let analyticsData = { /* objeto con datos recopilados */ };
window.addEventListener("unload", function() {
navigator.sendBeacon("/analytics", JSON.stringify(analyticsData));
});
- La solicitud se envía como POST.
- Podemos enviar no solo una cadena, sino también formularios y otros formatos, como se describe en el capítulo <info: fetch>, pero generalmente es un objeto string.
- Los datos están limitados por 64 kb.
Cuando finaliza la solicitud sendBeacon
, es probable que el navegador ya haya abandonado el documento, por lo que no hay forma de obtener la respuesta del servidor (que suele estar vacía para análisis).
También hay una bandera keepalive
para hacer tales solicitudes “after-page-left” en el método [fetch](info: fetch) para solicitudes de red genéricas. Puede encontrar más información en el capítulo <info: fetch-api>.
Si queremos cancelar la transición a otra página, no podemos hacerlo aquí. Pero podemos usar otro evento: onbeforeunload
.
window.onbeforeunload
Si un visitante inició la navegación fuera de la página o intenta cerrar la ventana, el controlador beforeunload
solicita una confirmación adicional.
Si cancelamos el evento, el navegador puede preguntar al visitante si está seguro.
Puede probarlo ejecutando este código y luego recargando la página:
window.onbeforeunload = function() {
return false;
};
Por razones históricas, devolver una cadena no vacía también cuenta como cancelar el evento. Hace algún tiempo, los navegadores solían mostrarlo como un mensaje, pero como dice la especificación moderna, no deberían.
Aquí hay un ejemplo:
window.onbeforeunload = function() {
return "Hay cambios sin guardar. ¿Salir ahora?";
};
El comportamiento se modificó, porque algunos webmasters abusaron de este controlador de eventos mostrando mensajes engañosos y molestos. Entonces, en este momento, los navegadores antiguos aún pueden mostrarlo como un mensaje, pero aparte de eso, no hay forma de personalizar el mensaje que se muestra al usuario.
event.preventDefault()
no funciona desde un manejador beforeunload
Esto puede sonar extraño, pero la mayoría de los navegadores ignoran event.preventDefault()
.
Lo que significa que el siguiente código puede no funcionar:
window.addEventListener("beforeunload", (event) => {
// no funciona, así que el manejador de evento no hace nada
event.preventDefault();
});
En lugar de ello, en tales manejadores uno debe establecer event.returnValue
a un string para obtener un resultado similar al pretendido en el código de arriba:
window.addEventListener("beforeunload", (event) => {
// funciona, lo mismo que si devolviera desde window.onbeforeunload
event.returnValue = "Hsy cambios sin grabar. ¿Abandonar ahora?";
});
readyState
¿Qué sucede si configuramos el controlador DOMContentLoaded
después de cargar el documento?
Naturalmente, nunca se ejecutará.
Hay casos en los que no estamos seguros de si el documento está listo o no. Nos gustaría que nuestra función se ejecute cuando se cargue el DOM, ya sea ahora o más tarde.
La propiedad document.readyState
nos informa sobre el estado de carga actual.
Hay 3 valores posibles:
"loading"
– el documento se está cargando."interactive"
– el documento fue leído por completo."complete"
– el documento se leyó por completo y todos los recursos (como imágenes) también se cargaron.
Entonces podemos verificar document.readyState
y configurar un controlador o ejecutar el código inmediatamente si está listo.
Como esto:
function work() { /*...*/ }
if (document.readyState == 'loading') {
// cargando todavía, esperar el evento
document.addEventListener('DOMContentLoaded', work);
} else {
// DOM está listo!
work();
}
También existe el evento readystatechange
que se activa cuando cambia el estado, por lo que podemos imprimir todos estos estados así:
// estado actual
console.log(document.readyState);
//imprimir los cambios de estado
document.addEventListener('readystatechange', () => console.log(document.readyState));
El evento readystatechange
es una mecánica alternativa para rastrear el estado de carga del documento, apareció hace mucho tiempo. Hoy en día, rara vez se usa.
Veamos el flujo de eventos completo para ver si están completados.
Aquí hay un documento con <iframe>
, <img>
y controladores que registran eventos:
<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="https://en.js.cx/clipart/train.gif" id="img">
<script>
img.onload = () => log('img onload');
</script>
El ejemplo práctico está en el sandbox.
La salida típica:
- [1] readyState inicial: loading
- [2] readyState: interactive
- [2] DOMContentLoaded
- [3] iframe onload
- [4] img onload
- [4] readyState: complete
- [4] window onload
Los números entre corchetes denotan el tiempo aproximado en el que ocurre. Los eventos etiquetados con el mismo dígito ocurren aproximadamente al mismo tiempo (+ – unos pocos ms).
document.readyState
se convierte eninteractive
justo antes deDOMContentLoaded
. Estas dos cosas realmente significan lo mismo.document.readyState
se convierte encomplete
cuando se cargan todos los recursos (iframe
eimg
). Aquí podemos ver que ocurre aproximadamente al mismo tiempo queimg.onload
(img
es el último recurso) ywindow.onload
. Cambiar al estadocomplete
significa lo mismo que “window.onload”. La diferencia es quewindow.onload
siempre funciona después de todos los demás controladoresload
.
Resumen
Eventos de carga de página:
- El evento
DOMContentLoaded
se activa en eldocument
cuando el DOM está listo. Podemos aplicar JavaScript a elementos en esta etapa.- Secuencias de comandos como
<script> ... </script>
o<script src =" ... "> </script>
bloquean DOMContentLoaded, el navegador espera a que se ejecuten. - Las imágenes y otros recursos también pueden seguir cargándose.
- Secuencias de comandos como
- El evento
load
enwindow
se activa cuando se cargan la página y todos los recursos. Rara vez lo usamos, porque generalmente no hay necesidad de esperar tanto. - El evento
beforeunload
enwindow
se activa cuando el usuario quiere salir de la página. Si cancelamos el evento, el navegador pregunta si el usuario realmente quiere irse (por ejemplo, tenemos cambios sin guardar). - El evento
unload
enwindow
se dispara cuando el usuario finalmente se está yendo, en el controlador solo podemos hacer cosas simples que no impliquen demoras o preguntas al usuario. Debido a esa limitación, rara vez se usa. Podemos enviar una solicitud de red connavigator.sendBeacon
. document.readyState
es el estado actual del documento, los cambios se pueden rastrear con el eventoreadystatechange
:loading
– el documento esta cargando.interactive
– el documento se analiza, ocurre aproximadamente casi al mismo tiempo queDOMContentLoaded
, pero antes.complete
– el documento y los recursos se cargan, ocurre aproximadamente casi al mismo tiempo quewindow.onload
, pero antes.