El proceso de renderizado en los navegadores

Diagrama de flujo de carga de una web

El renderizado es el proceso del navegador para pintar en pantalla una página web gestionando los recursos HTML y CSS principalmente. El motor de renderizado (rendering engine) del navegador es la parte que lleva a cabo este proceso de carga de una página web. Suele realizarse en los navegadores en dos hilos que se ejecutan paralelamente, uno para el HTML y otro para el CSS. Por un lado se parsea el HTML para extraer los elementos y construir el DOM. Al mismo tiempo se parsea el CSS que puede estar en elementos <style> y también en archivos externos vinculados con <link rel="stylesheet" src="..." />. Se trata de construir un conjunto de reglas de estilo. Cuando ambos procesos finalizan se realiza el layout, una tarea de unificación aplicando estilos de las reglas CSS a los elementos del DOM. El árbol de renderizado obtenido servirá de base para realizar el pintado final. Puede ver con más detalle el flujo de carga del renderizado en un documento de Tali Garsiel.

Lo que nos interesa destacar es que para llegar al layout hemos de construir las reglas de estilo. Ambos procesos paralelos tienen que finalizar para proseguir con el layout. Si uno de ellos es bloqueado por alguna razón, el layout debe esperar aunque el otro proceso ya hubiese finalizado. Inicialmente se recibe el HTML y a medida que se parsea se envía el CSS de los elementos <style> al parseador de CSS. Luego si encuentra un <link rel="stylesheet" src="..."> el navegador cursará inmediatamente una petición de ese recurso, un archivo con CSS externo. Si ese recurso tarda mucho en cargarse puede suceder que el hilo que parsea el DOM ya haya finalizado y tenga que esperar a que el proceso CSS finalice.

Bloqueo de visualización y optimizar la entrega CSS

Hice una serie de cambios importantes en este sitio en el mes de diciembre del año 2013. Pero antes de hacer esos cambios comprobé con herramientas como Pagespeed Insights los problemas que tenía. Guardé una versión de la estructura anterior de la página de entrada al sitio, incluso separando una copia de los recursos CSS, JS y otros vinculados con esa página. Pasando un test en Pagespeed Insights a esa página se obtiene una puntuación 70/100 en móviles y 86/100 en ordenadores. Puedes ver en PDF ambos resultados en móviles y en ordenadores.

Muchos de los problemas que aparecen se pueden corregir con cierta facilidad. Especialmente lo referente a minimizar o minificar los recursos. Especificar caché y habilitar compresión es también relativamente fácil de hacer. De hecho es algo que ya he conseguido llevar a cabo en este sitio obteniendo 100/100 tanto en móviles como en ordenadores. Pero antes tuve que solucionar dos de los problemas que aparecen en ese test:

(1) Eliminar el JavaScript que bloquea la visualización y el CSS del contenido de la mitad superior de la página

Tu página tiene 5 recursos CSS que provocan un bloqueo. Ello causa un retraso en el procesamiento de la página. No se ha podido visualizar el contenido de la mitad superior de la página sin tener que esperar a que se cargara alguno de los recursos. Intenta aplazar o cargar de forma asíncrona los recursos que bloquean la visualización, o bien inserta porciones críticas de dichos recursos directamente en el HTML. Optimizar la entrega de CSS de estas URL: cabeza-pie.css, interior.css, formatos.css, form-emerge.css, canvas-clock.css.

El título se refiere a bloqueos producidos tanto por JavaScript como por CSS, que actuando sobre la mitad superior de la página retrasan su visualización. Pero el problema en este caso se plantea sólo para el CSS. El JavaScript no produce bloqueo tras unas mejoras que ya había implementado en el año 2012. Se basa en el uso de la carga asíncrona de los scripts y del cargador de módulos. En el comentario del test aparece un enlace a documentación de Developers de Google Optimize CSS Delivery en relación con optimizar la entrega CSS.

(2) Prioriza el contenido visible

Tu página requiere indicaciones completas de red adicionales para procesar el contenido destacado en la parte superior de la página. Para obtener un rendimiento óptimo, reduce la cantidad de HTML necesario para procesar dicho contenido. Se ha precisado 42,8 KB de la respuesta para poder mostrar contenido en la mitad superior de la página. Prioriza el contenido visible necesario para poder mostrar contenido en la mitad superior de la página.

Aquí la documentación del test nos lleva a Reduce the size of the above-the-fold content para reducir el tamaño del contenido above-the-fold. ¿Qué es eso de above the fold?

En estos dos temas intentaré exponer la causa de estos dos problemas y la solución que ya he implementado en este sitio.

Recursos externos que bloquean la carga de la página

Expondré primero como un recurso CSS externo puede bloquear la carga de la página. Para llevar a cabo una prueba tenemos una página de un primer ejemplo bloq.html con un archivo CSS externo vinculado con <link rel="stylesheet">. Se trata del archivo formatos.css donde antes ponía todo el estilo de formato para el sitio. La página de ejemplo sólo hace uso de un par de reglas relacionadas con los encabezados, pero nos interesa un archivo de un tamaño importante para poder ver el efecto de la prueba. Luego hacemos otra versión de la misma página no-bloq.html pero insertando todo el CSS de ese archivo externo ahora en un elemento <style> en la cabecera del documento. Esto es lo que se llama estilo interno o inline, en el sentido de estilo en línea con el documento que se ubica en el <head>. Hay que diferenciarlo del estilo en línea con el elemento que se ubica en el atributo style, también a veces denominado inline pero en relación al elemento.

Obtengo dos capturas de pantalla en el Timeline de Chrome, grabando todo lo que ocurre desde que se hace la petición hasta que se carga el documento:

He ajustado las ventanas del timeline para que tengan la misma graduación y he anotado los tiempos de los eventos que interesan. En la siguiente imagen he recortado ambas y las pongo en una misma imagen para compararlas mejor:

Compara bloqueo CSS externo e inline

Esto es un resumen comparativo de ambas pruebas hechas en localhost, por lo que los tiempos solo interesan desde el punto de vista de comparar ambos ejemplos.

Núm.Eventoms con CSS
externo
ms con CSS
interno
1Send Request HTML0 ms0 ms
2Finish Loading HTML58 ms57 ms
2.aSend Request CSS76 msn.a.
2.bFinish Loading CSS159 msn.a.
3Paint164 ms83 ms

Se envía la petición (Send Request) (1) del HTML. El primer ejemplo tiene un HTML con tamaño de 3.77 KB pero la salida se comprime con GZIP y finalmente llegan 2.0 KB. El archivo CSS externo está ya minificado con un tamaño de 7.11 KB y llega comprimido con 2.5 KB. En cuanto al segundo ejemplo, el HTML con el CSS interno ocupa 10.8 KB y llega comprimido con 4.1 KB. En ambos casos los HTML no tienen un tamaño muy diferente (2.0 KB y 4.1 KB ), por lo que el evento finalización de carga (Finish Loading) se produce más o menos al mismo tiempo: 58 ms y 57 ms respectivamente (2).

El navegador recibe el HTML del primer ejemplo y empieza a parsearlo encontrando el elemento <link rel="stylesheet" href="formatos.css" />. Inmediatamente envia otra petición a 76 ms (2.a) para obtener ese recurso. Sigue parseando (Parse HTML) y también recalculando el estilo (Recalculate Style) que hasta ese momento tiene. Pero tiene que esperar por el CSS que finaliza la carga a los 159 ms (2.b). Sólo entonces puede hacer el layout, aunque previamente recalcula estilo otra vez, y tras eso por fin hace el pintado (Paint) de la página a los 164 ms (3).

En cambio en el segundo ejemplo el navegador no tiene ningún recurso CSS que recuperar externamente. Todo el estilo ya se cargó en el <head> y cuando finalice el parseado del HTML podrá seguir de forma ininterrumpida recalculando estilo, haciendo layout y finalmente pintando. De esta forma consigue pintar a los 83 ms, aproximadamente a la mitad del tiempo que el primer ejemplo. Se observa claramente como el CSS externo bloquea la carga de la página de ese ejemplo. Y esto en cualquier caso de que tenga que recuperar un CSS aunque las reglas del mismo no apliquen a la página. El navegador no sabe esto previamente y no le queda más remedio que esperar a cargar el CSS antes de pintar nada en pantalla.

Optimizar entrega CSS: estilo externo versus interno

El aviso sobre optimizar entrega CSS se activa en Pagespeed Insights cuando un CSS externo bloquea el renderizado y por tanto demora el pintado de la página, tal como vimos antes. Esto se resuelve usando sólo CSS interno. Pero si el tamaño de ese CSS es muy largo hay que priorizar el contenido visible tal como veremos más abajo. Supongamos que en cualquier caso decidimos que todo o una gran parte del CSS de nuestro sitio sea siempre interno. Usualmente disponemos de varios archivos CSS donde configuramos el estilo agrupándolo por funcionalidades del sitio. Con la estructura anterior de las páginas de este sitio antes de la remodelación tenía estos archivos CSS:

FuncionalidadArchivo CSSTamaño KBVinculado
en páginas
Componentes
de estructura
cabeza-pie.css7Todas
interior.css5Todas
formatos.css8Todas
Otros
Componentes
resaltador.css3Muchas
pasa-fotos.css3Algunas
........

En la parte de otros componentes y para no alargar esa tabla he omitido hasta 11 archivos más que se usaban en algunas páginas, incluso algunos sólo en dos o tres páginas. La mayor parte no pasaban de 3 KB. Los tamaños son ya minimizados (o minificados) pero antes de salir comprimidos del servidor. Podemos recopilar todo esos CSS en un único archivo. Y de hecho para este nuevo sitio ya reformado así lo hice, creando un nuevo archivo base.css que ocupa 30 KB minimizado y antes de comprimir. Si metemos esos 30 KB de CSS en la cabecera de todos los documentos evitando una petición de archivo externo tendremos estas ventajas y desventajas:

  • Ventajas con CSS interno
    • Evitamos el bloqueo de recursos CSS externos (y por tanto optimizamos la entrega CSS).
    • Liberamos conexiones TCP que se destinarán a recuperar otros recursos como archivos JS o de imágenes. Los navegadores suelen gestionar unas 6 conexiones simultáneas por dominio para recuperar recursos externos en paralelo.
  • Desventajas con CSS interno
    • La mayor parte del estilo no será usado en todas las páginas y el navegador estará renderizando CSS sin necesidad alguna.
    • Si el tamaño del CSS interno es grande en comparación con el HTML es posible que perjudique la carga, lo que no sucede cuando recuperamos el CSS desde varios archivos externos aprovechándonos de las conexiones en paralelo.
    • No es muy eficiente a la hora de mantener el CSS, pues cualquier modificación nos lleva a recorrer todas las páginas donde estuviese ese estilo.
    • No nos aprovechamos del cacheado de recursos CSS externos. Usualmente el documento HTML no se cachea o se cachea con menores tiempos de vida.

Precisamente la primera ventaja es lo que buscamos. Pero son tan importantes las tres primeras desventajas que parece conducirnos a un callejón sin salida. Las dos primeras desventajas podrían dar lugar a que aparezca un mensaje sobre priorizar el contenido visible en Pagespeed Insights.

Priorizar el contenido visible: Above the fold

El concepto above the fold proviene de los periódicos que se exponen a los clientes doblados por la mitad. Así los contenidos de mayor impacto se localizan en esa parte superior de la página. En desarrollo web se refiere a la porción de la página que será visible sin hacer scroll. Aunque podría aplicarse desde una perspectiva de la importancia de los contenidos, ahora sólo nos interesa en lo relacionado con la carga de la página.

La idea general es cargar cuanto antes el estilo que afecta a la parte superior de la página, contenido que resultará visible cuando ésta se cargue. Y diferir el resto hasta un momento posterior a la carga. Aquí "cargar cuanto antes" significa estilo en línea en la cabecera del documento, antes de cualquier otro recurso CSS o JS. Nada de estilo externo con <link rel="stylesheet">. Pero si ponemos todo el CSS del sitio en la página hay que resolver el problema de las reglas de estilo que no aplican a esa página y especialmente a su parte superior.

No es fácil optimizar la entrega CSS y priorizar el contenido visible sólo en el navegador. Necesitamos alguna gestión en el lado del servidor y en eso PHP nos puede ayudar. Debemos conseguir pre-procesar el archivo base.css con todo el estilo del sitio para inyectar con PHP en un elemento <style> del <head> sólo el CSS que afecta a la parte superior de la página. También debemos disponer en el navegador el estilo de la parte inferior para inyectarlo con JavaScript tras el evento onload. Esto lo podríamos hacer cuando el usuario haga click o scroll por primera vez en la página. Esta es la idea básica de una técnica que denominaré CSS-FOLD y que se desarrolla en el tema siguiente.