Mejorar el tiempo de carga de una página web usando lazy load

Escrito por el , actualizado el .
planeta-codigo programacion web
Enlace permanente Comentarios

Las páginas grandes y con muchos recursos como imágenes e iframes implementar la carga vaga o lazy load obtienen un gran beneficio, necesitando realizar menos peticiones en la carga inicial, con menos tamaño y cargándose en menos tiempo. Los navegadores han añadido soporte para desde JavaScript proporciona este soporte.

Firefox

Chromium

El tiempo de carga de una página web es una métrica importante para una buena experiencia de usuario, es una métrica evaluada por PageSpeed y también de cara al posicionamiento en los buscadores o SEO ya que influye en la fórmula que usa Google para clasificar y posicionar las páginas en la página de resultados de búsqueda, un buen posicionamiento en los buscadores es importante porque significa más visitas a una web que en muchos casos es la mayor fuente de usuarios. Un tiempo de carga alto hace que los usuarios abandonen la página antes de que esté cargada completamente, influyendo en el porcentaje de rebote y el tiempo de visita que se puede medir con Google Analytics.

Una de las variables que influyen en el tiempo de carga es el número de peticiones y el tamaño de los recursos de esas peticiones que se hacen al servidor para descargar los elementos de la página completa, estos son imágenes, hojas de estilo, archivos de JavaScript, iframes, … Sin embargo, los navegadores cargan todos los elementos de una página incluso aquellos que están en la parte baja e inicialmente no se ven hasta que el usuario se desplaza hasta visualizarlos. Cargar elementos que no se visualizan es innecesario e ineficiente en el navegador pero también para el servidor que ha de atender a más peticiones.

Los artículos de mi blog como Desempaquetado de PlayStation 4 Slim de 1 TB o Desempaquetado Intel NUC8i5BEK (Bean Canyon), HyperX Impact (RAM) y Samsung 970 EVO NVMe (SSD), incluyen numerosas imágenes e iframes de vídeos de Youtube y publicidad de Amazon, en todas se carga los comentarios de Disqus y para compartir los artículos con ShareThis que están al final de la página y hasta que el usuario no ha leído el artículo son innecesarios. Disqus además en concreto para cargarse realiza numerosas peticiones adicionales. Estás páginas son de las más complejas por número de recursos y extensión del artículo que tengo en el blog por lo que he analizado una de ellas antes y después aplicando lazy loading para cargar inicialmente solo los elementos que se visualizan que son aquellos que están en la parte superior de la página.

Página PlayStation Página PlayStation

Página Intel NUC Página Intel NUC

Páginas representativas de Blog Bitix

Analizando el número de peticiones, tamaño y tiempo de carga en la página de la PlayStation realizaban 343 peticiones inicialmente, con un tamaño de descarga de 5 MiB en un tiempo de carga según Firefox de 15 segundos. Evaluando esta página con PageSpeed un aspecto importante que indica a mejora reducir el número de elementos descargados, además de reducir peticiones y tamaño de la página se descarguen inicialmente los elementos importantes y omitiendo recursos de JavaScript hace que el desempeño sea mejor. El resultado es un menor tiempo de carga.

Métricas de carga en PageSpeed página PlayStation antes Métricas de carga de PageSpeed página Intel NUC antes

Métricas de carga en PageSpeed antes

Métricas de carga de Firefox página PlayStation antes

Métricas de carga en Firefox

La solución es cargar los elementos imágenes, iframes, vídeos y comentarios de Disqus cuando se vayan a visualizar al desplazarse el usuario hasta ellos. Esto reduce notablemente el número de peticiones realizadas inicialmente, el tamaño de descarga y el tiempo de carga. Una librería de JavaScript que permite realizar esta funcionalidad es Lozad, no tiene dependencias, es muy pequeña, es soprendentemente fácil de utilizar en relación con el beneficio que aporta. Aprovecha el soporte de la interfaz IntersectionObserver precisamente proporcionada por los navegadores para realizar la carga vaga o lazy load de forma eficiente.

Para las imágenes e iframes hay que añadir una clase a los elementos de HTML y cambiar el atributo src a data-src. Los comentarios de Disqus se cargan con un archivo de JavaScript por lo que hay que retrasar la carga hasta que el elemento HTML que los contiene se visualice y con una función de callback se inicia la inserción de su recurso de JavaScript utilizando jQuery para cargar insertar en la página dinámicamente recursos JavaScript.

Este es una plantilla que utilizo para generar de forma estática el contenido del blog con Hugo en el que las imágenes ilustrativas de los artículos usan el atributo data-src e incluyen la clase de CSS lozad, en el código fuente o utilizando la función Inspeccionar se comprueba el HTML resultado. El otro archivo parte del JavaScript de este blog que implementa la carga vaga de las imágenes, iframes y scritps de Disqus y ShareThis. Para el caso de insertar JavaScript dinámicamente se observa un elemento con una expresión de jQuery y mediante una función callback que Lozad invoca cuando su elemento se visualiza momento en que se realiza la acción de insertar el recurso de JavaScript.

1
2
3
4
5
6
7
8
<div class="media">
    <figure>
    <a href="{ $imagescaled1.RelPermalink }" title="{ .Params.title1 }" data-gallery="data-gallery"><img data-src="{ $imagescaledthumb1.RelPermalink }" width="{ $imagescaledthumb1.Width }" height="{ $imagescaledthumb1.Height }" layout="responsive" class="lozad"></a>
    <a href="{ $imagescaled2.RelPermalink }" title="{ .Params.title2 }" data-gallery="data-gallery"><img data-src="{ $imagescaledthumb2.RelPermalink }" width="{ $imagescaledthumb2.Width }" height="{ $imagescaledthumb2.Height }" layout="responsive" class="lozad"></a>
    <a href="{ $imagescaled3.RelPermalink }" title="{ .Params.title3 }" data-gallery="data-gallery"><img data-src="{ $imagescaledthumb3.RelPermalink }" width="{ $imagescaledthumb3.Width }" height="{ $imagescaledthumb3.Height }" layout="responsive" class="lozad"></a>
    <figcaption>{ .Params.caption }{ if .Params.source }<br/><small>{ i18n "source" }: { .Params.source }</small>{ end }</figcaption>
    </figure>
</div>
figureproc.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
require(['jquery', 'lozad', 'jquery-blueimp-gallery'], function ($, lozad, blueimp) {
    ...

    function initLazyload() {
        var observer = lozad('.lozad', {
            rootMargin: '50px 0px'
        });

        var disqusObserver = lozad('#disqus_thread', {
            rootMargin: '50px 0px',
            load: function(el) {
                $.ajax({
                    url: '//' + disqus_shortname + '.disqus.com/' + disqus_script,
                    async: true,
                    cache: true,
                    dataType: 'script',
                    success: function() {}
                });
            }
        });

        var shareThisObserver = lozad('div.sharethis-inline-share-buttons', {
            rootMargin: '50px 0px',
            load: function(el) {
                $.ajax({
                    url: '//platform-api.sharethis.com/js/sharethis.js#property=5920c4ce1bd0670011e06acd&product=inline-share-buttons',
                    async: true,
                    cache: true,
                    dataType: 'script',
                    success: function() {}
                });
            }
        });

        observer.observe();
        disqusObserver.observe();
        shareThisObserver.observe();
    }
    
    ...
    initLazyload();
});

main.js

Con simplemente esta mejora, que no es complicada de realizar, añadiendo el código anterior y realizando los sencillos cambios en los atributos de imágenes e iframes las métricas en la comparación con página anterior mejoran notablemente pasando apróximadamente de 15 segundos a menos de 4 en un tiempo de carga hasta que el navegador dejan de hacer peticiones.

Página Prueba Antes Después
PlayStation Firefox 343 peticiones, 5 MiB, 15s 104 peticiones, 1.5 MiB, 3.5s
PlayStation PageSpeed 23 44
Intel NUC PageSpeed 9 38

La métrica de PageSpeed mejoran notablemente, es una cifra sobre 100 que aún dista de ser alta debido a que Analytics y AdSense imponen una fuerte penalización por utilizarlos e incluirlos en las páginas. La página WebPageTest proporciona algunos datos adicionales y complementarios a los proporcinados por PageSpeed, también es recomendable usarla para medir la variación en los resultados con los cambios realizados con el objetivo de mejorar una página.

Métricas de carga en PageSpeed página PlayStation después Métricas de carga de PageSpeed página Intel NUC después

Métricas de carga en PageSpeed después

Métricas de carga de Firefox página PlayStation después

Métricas de carga en Firefox

Los navegadores van a añadir el soporte de carga vaga directamente en las imágenes e iframes con un el atributo loading mediante el cual el JavaScript anterior será innecesario para estos elementos.

En definitiva es un pequeño cambio sencillo de realizar y que mejora notablemente la experiencia de usuario, la carga del servidor, es recomendable para el SEO incluso desde el punto de vista de la privacidad de los usuarios.

Otro uso distinto para la carga vaga es lanzar eventos de Analytics, esto lo he empleado para saber si los usuarios llegan al final de los artículos. Con esto es posible obtener datos interesantes sobre cuales son las mejoras páginas o artículos, por ejemplo, para diferencia entre página muy visitada por que esté bien posicionada pero ni interesante para los usuarios porque no llegan al final del artículo o por el contrario páginas con pocas visitas pero que los usuarios las leen hasta el final.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
require(['jquery', 'lozad'], function($, lozad) {
    function initAnalytics() {
        ...

        var pageBottomObserver = lozad('#pageBottom', {
            rootMargin: '50px 0px',
            load: function(el) {
                ga('send', 'event', 'page', 'show', 'bottom', {'nonInteraction': 1});
            }
        });
        pageBottomObserver.observe();
    }

    ...

    initAnalytics();
    ...
});
app.js


Comparte el artículo: