Llevamos tanto tiempo conviviendo con HTML, con su etiqueta <head>
y su <body>
, que es bastante probable que muchos no se hayan preguntado el porqué de esta distribución, o cómo puede afectar el alterarla.
No se trata sólo de una cuestión estética de limpieza y orden del código, sino que tiene relevancia en el tiempo de carga de la página y en la experiencia del usuario mientras se realiza dicha carga. Muchos estudios determinan que el tiempo medio de carga de una web está en torno a 7 segundos, y todo lo que lo supere hace perder visitantes que se marchan de la página antes de que cargue.
Para entender por qué es preferible colocar uno u otro elemento en una posición concreta, lo primero que hay que entender es cómo se procesa el código HTML.
El parser HTML lee las distintas etiquetas del documento y conforme se las va encontrando las añade al árbol DOM (Modelo de Objetos del Documento), una estructura de datos que permite manipular de modo más sencillo el modelo. También a medida que se crea el árbol DOM, se van renderizando los nodos ya procesados, de modo que al usuario la carga de la página le parezca gradual y no repentina al final.
Por tanto, tenemos el primer factor determinante en el tiempo de carga de una página: el tamaño de su árbol DOM. Resulta obvio que cuanto mayor sea, más tardará la página en cargarse. Y otro aspecto que también cae por su propio peso: los elementos que se encuentren antes en el documento, se dibujarán antes.
Por eso, es conveniente procurar que los elementos principales del contenido se muestren antes, de modo que el usuario tenga acceso a ellos aunque la página continúe a medio cargar. Por ejemplo, si tenemos una página con un título, una noticia simple y un menú muy complejo, lo mejor es situar primero en el HTML los elementos del título y la noticia, y que luego el CSS se encargue de organizarlos visualmente. Así, el visitante podrá empezar a leer el contenido que le interesa en lugar de esperar a que se carguen decenas de enlaces y botones que quizá no llegue a usar.
Aparte del tamaño y orden del DOM, también es conveniente conocer cómo funcionan la carga de elementos en paralelo y las interacciones con el árbol del documento.
Carga de elementos en paralelo
Como bien sabéis, no todo el contenido de la página está expresado en el texto del HTML en forma de etiquetas, atributos y literales, sino que se hace uso de distintos recursos externos enlazados: imágenes, hojas de estilo, marcos, objetos flash...
Estos recursos pueden llegar a tener un tamaño muy superior al del propio HTML y sería muy incómodo tener que descargarlos por completo antes de poder parsear el siguiente elemento. Es por eso que se descargan en paralelo, abriendo nuevas conexiones.
Lo que mucha gente desconoce es que cada navegador tiene una limitación en el número de elementos que se pueden descargar a la vez, siendo además un número tan aparentemente pequeño como 4, 6 u 8 en los más usuales (Firefox, Chrome, IExplorer, etc). Y aunque se pueden modificar parámetros como network.http.max-connections-per-server
y network.http.pipelining.maxrequests
, lo normal es esperar que nuestros usuarios no lo hagan por mero desconocimiento.
De esta limitación se deduce que si tenemos muchos recursos externos, aunque algunos de ellos se vayan solicitando en paralelo, sí que habrá que esperar a que terminen de descargarse completamente los primeros para ir liberando conexiones y poder proceder con los siguientes.
En el caso de elementos externos que contienen código, como puede ser el CSS o JavaScript, la principal recomendación es unificarlos en lugar de tenerlos desperdigados en muchos ficheros pequeños. Incluso, si no queremos o no podemos hacerlo manualmente, existen módulos de servidor para poder reescribir las llamadas, de modo que una cabecera como ésta:
<head> <link rel="stylesheet" type="text/css" href="styles/yellow.css"> <link rel="stylesheet" type="text/css" href="styles/blue.css"> <link rel="stylesheet" type="text/css" href="styles/big.css"> <link rel="stylesheet" type="text/css" href="styles/bold.css"> </head>
se convierta en otra como ésta:
<head> <link rel="stylesheet" type="text/css" href="styles/yellow.css+blue.css+big.css+bold.css.pagespeed.cc.xo4He3_gYf.css"> </head>
¿Y por qué decíamos en el título que el CSS es preferible que esté arriba? Porque si lo invocásemos al final de la página, en el caso de documentos con un árbol DOM muy grande veríamos una página plana, como las que abundaban en los primeros tiempos de Internet, hasta que se llegue al final y se solicite la carga de los estilos. Esto puede dar la sensación de página inacabada, o que el usuario no reconozca la web sin los estilos y piense que se ha equivocado, y que en ambos casos abandone el sitio antes de finalizar.
Al realizarse la descarga en paralelo, si el CSS es el primer recurso externo al que llamamos en el <head>
, nos aseguraremos que la página poseerá estilos en el momento más temprano posible.
Por otra parte, las imágenes son normalmente el recurso que más tarda en cargarse por su tamaño. La principal recomendación al respecto es definir siempre en la etiqueta <img>
los atributos de altura y ancho para que se pueda reservar su espacio en la ventana mucho antes de que el fichero se descargue e interprete y, por supuesto, usar siempre imágenes del tamaño idóneo y con la mejor compresión posible. Si tienes una galería con muchas imágenes, las miniaturas deben ser eso, miniaturas. Y no sólo por aligerar la carga, sino también por respeto a los usuarios que accedan desde dispositivos móviles, ya que a ellos no sólo les fastidiaríamos la experiencia de usuario, sino que además consumiríamos inútilmente su tarifa de datos.
Hilando muy muy fino, se pueden unificar todas las imágenes en una única tira, tal y como hemos recomendado anteriormente con el CSS, y acceder mediante sprites para cargar una u otra sección de la tira en cada uno de los espacios en los que queremos mostrar las fotos. Una técnica no muy habitual en entorno web, pero bastante efectiva y que puede aplicarse incluso con objetos de audio.
Interacciones con el árbol DOM
Si la web fuese un entorno estático, una vez que hemos cargado el DOM y los recursos externos se habrían terminado nuestras disquisiciones. Pero resulta que no, que es un entorno vivo, dinámico, en el que nos interesa desplegar menús, mostrar alertas, actualizar secciones, realizar pases de diapositivas... En definitiva, queremos que la web responda a nuestra interacción de modo eficiente, y para ello lo mejor es no cargar páginas completas sino modificar los elementos del DOM que nos interese.
Ahí es donde entran en juego las capacidades de JavaScript. Por ejemplo, el método document.write()
nos permite añadir nuevo contenido al DOM, interpretándose el texto escrito por esta función como si fuera parte del HTML original.
<script> var d = new Date(); document.write("Hoy es día <strong>", d.getDate(), "</strong>"); </script>
Cuando el parser se encuentra el script anterior, lo ejecutará de forma síncrona antes de pasar al siguiente elemento. Como resultado de la evaluación, añadirá el siguiente texto al árbol DOM y sólo entonces continuará evaluando el resto del HTML.
Hoy es día <strong>12</strong>
He aquí el motivo por el que decíamos que el JavaScript ha de ir lo más abajo posible. La evaluación del script ha de hacerse tan pronto como el parser se encuentre con la etiqueta <script>
, así que es preferible tener cargada la mayor parte del DOM antes de ponernos a interactuar con él.
No hablamos por tanto de la inclusión de ficheros .js externos, que quizá sólo contengan librerías con declaraciones de funciones, sino de cualquier expresión que deba ser evaluada o cualquier llamada a métodos que sí se realizaran en el mismo momento en que son leídas. Es este el motivo por el que numerosos scripts de analítica web, resaltado de sintaxis, etc. nos recomiendan que incluyamos su llamada justo antes de la etiqueta </body>
.
No obstante, en HTML5 se introdujo para los scripts el nuevo atributo async, que permite que un script externo sea ejecutado inmediatamente pero de forma asíncrona, dejando que el parser siga analizando el documento sin esperar a que la ejecución del script finalice.
Este tipo de ejecución, aparte de que sólo puede hacerse con scripts externos (aquellos con cuentan con un atributo src
), debe ser utilizada con precaución, ya que no tendremos certeza de qué elementos del DOM estarán o no cargados en el momento de ejecutar el script, por lo que corremos el riesgo de intentar acceder a elementos aún no instanciados.
Más información | We.Developers 010 - Optimización web
En Genbeta Dev | JQuery DOM y Rendimiento, Evita que tus visitantes se duerman con Google Page Speed