Carga diferida de imágenes

Las imágenes pueden aparecer en una página web porque están intercaladas en el HTML como elementos <img> o como imágenes de fondo de CSS. En esta publicación, descubrirás cómo realizar la carga diferida de ambos tipos de imágenes.

Imágenes intercaladas

Los candidatos más comunes para la carga diferida son las imágenes que se usan en los elementos <img>. Con las imágenes intercaladas, tenemos tres opciones para la carga diferida, que se pueden usar en combinación para lograr una mejor compatibilidad en navegadores:

Cómo usar la carga diferida a nivel del navegador

Tanto Chrome como Firefox admiten la carga diferida con el atributo loading. Este atributo se puede agregar a elementos <img> y también a elementos <iframe>. Un valor de lazy le indica al navegador que cargue la imagen de inmediato si está en el viewport y que recupere otras imágenes cuando el usuario se desplace cerca de ellas.

Consulta el campo loading de la tabla de compatibilidad del navegador de MDN para obtener detalles sobre la compatibilidad con los navegadores. Si el navegador no admite la carga diferida, se ignorará el atributo y las imágenes se cargarán de inmediato, como de costumbre.

Para la mayoría de los sitios web, agregar este atributo a las imágenes intercaladas mejora el rendimiento y permite que los usuarios carguen imágenes a las que es posible que nunca se desplacen. Si tienes una gran cantidad de imágenes y quieres asegurarte de que los usuarios de navegadores no admitan el beneficio de carga diferida, deberás combinar esta acción con uno de los métodos que se explican a continuación.

Para obtener más información, consulta el artículo sobre la carga diferida en el nivel del navegador para la Web.

Usa Intersection Observer

En el caso de la carga diferida de polyfills de los elementos <img>, usamos JavaScript para verificar si se encuentran en el viewport. Si es así, sus atributos src (y, a veces, srcset) se propagan con URLs que dirigen al contenido de la imagen deseado.

Si escribiste código de carga diferida anteriormente, es posible que hayas logrado tu tarea con controladores de eventos, como scroll o resize. Si bien este enfoque es el más compatible con todos los navegadores, los navegadores modernos ofrecen una manera más eficaz y eficiente de verificar la visibilidad de los elementos a través de la API de Intersection Observer.

Intersection Observer es más fácil de usar y leer que el código que depende de varios controladores de eventos, ya que solo debes registrar un observador para observar los elementos, en lugar de escribir un código de detección de visibilidad de elementos tedioso. Lo único que queda por hacer es decidir qué hacer cuando un elemento es visible. Supongamos que este es el patrón de lenguaje de marcado básico para los elementos <img> cargados de forma diferida:

<img class="lazy" src="placeholder-image.jpg" data-src="image-to-lazy-load-1x.jpg" data-srcset="image-to-lazy-load-2x.jpg 2x, image-to-lazy-load-1x.jpg 1x" alt="I'm an image!">

Hay tres partes relevantes de este lenguaje de marcado en las que debes enfocarte:

  1. El atributo class, que es con lo que seleccionarás el elemento en JavaScript.
  2. El atributo src, que hace referencia a una imagen de marcador de posición que aparecerá cuando la página se cargue por primera vez
  3. Los atributos data-src y data-srcset, que son atributos de marcador de posición que contienen la URL de la imagen que cargarás una vez que el elemento esté en el viewport

Ahora, veamos cómo usar Intersection Observer en JavaScript para cargar imágenes de forma diferida con este patrón de lenguaje de marcado:

document.addEventListener("DOMContentLoaded", function() {
  var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));

  if ("IntersectionObserver" in window) {
    let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          let lazyImage = entry.target;
          lazyImage.src = lazyImage.dataset.src;
          lazyImage.srcset = lazyImage.dataset.srcset;
          lazyImage.classList.remove("lazy");
          lazyImageObserver.unobserve(lazyImage);
        }
      });
    });

    lazyImages.forEach(function(lazyImage) {
      lazyImageObserver.observe(lazyImage);
    });
  } else {
    // Possibly fall back to event handlers here
  }
});

En el evento DOMContentLoaded del documento, esta secuencia de comandos consulta al DOM todos los elementos <img> con una clase lazy. Si Intersection Observer está disponible, crea un nuevo observador que ejecute una devolución de llamada cuando los elementos img.lazy ingresen al viewport.

Intersection Observer está disponible en todos los navegadores modernos. Por lo tanto, usarlo como polyfill para loading="lazy" garantizará que la carga diferida esté disponible para la mayoría de los visitantes.

Imágenes en CSS

Si bien las etiquetas <img> son la forma más común de usar imágenes en páginas web, las imágenes también se pueden invocar mediante la propiedad background-image de CSS (y otras propiedades). La carga diferida a nivel del navegador no se aplica a las imágenes de fondo CSS, por lo que debes considerar otros métodos si tienes imágenes de fondo para cargar de forma diferida.

A diferencia de los elementos <img>, que se cargan independientemente de su visibilidad, el comportamiento de carga de imágenes en CSS se realiza con más especulación. Cuando se compilan los modelos de documentos y objetos de CSS y el árbol de renderización, el navegador examina cómo se aplica CSS a un documento antes de solicitar recursos externos. Si el navegador determina que una regla de CSS que implica que un recurso externo no se aplica al documento en su construcción actual, el navegador no lo solicita.

Este comportamiento especulativo se puede usar para diferir la carga de imágenes en CSS. Para ello, se usa JavaScript para determinar cuándo un elemento se encuentra en el viewport y, luego, se aplica una clase a ese elemento que invoque el estilo de una imagen de fondo. Esto hace que la imagen se descargue en el momento en que se necesite y no en la carga inicial. Por ejemplo, analicemos un elemento que contiene una gran imagen de fondo hero:

<div class="lazy-background">
  <h1>Here's a hero heading to get your attention!</h1>
  <p>Here's hero copy to convince you to buy a thing!</p>
  <a href="/buy-a-thing">Buy a thing!</a>
</div>

Por lo general, el elemento div.lazy-background contendrá la imagen de fondo hero invocada por algún CSS. Sin embargo, en este ejemplo de carga diferida, puedes aislar la propiedad background-image del elemento div.lazy-background mediante una clase visible que se agrega al elemento cuando está en el viewport:

.lazy-background {
  background-image: url("hero-placeholder.jpg"); /* Placeholder image */
}

.lazy-background.visible {
  background-image: url("hero.jpg"); /* The final image */
}

A partir de aquí, usa JavaScript para verificar si el elemento está en el viewport (con Intersection Observer) y agrega la clase visible al elemento div.lazy-background en ese momento, que carga la imagen:

document.addEventListener("DOMContentLoaded", function() {
  var lazyBackgrounds = [].slice.call(document.querySelectorAll(".lazy-background"));

  if ("IntersectionObserver" in window) {
    let lazyBackgroundObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          entry.target.classList.add("visible");
          lazyBackgroundObserver.unobserve(entry.target);
        }
      });
    });

    lazyBackgrounds.forEach(function(lazyBackground) {
      lazyBackgroundObserver.observe(lazyBackground);
    });
  }
});

Efectos en el procesamiento de imagen con contenido más grande (LCP)

La carga diferida es una gran optimización que reduce el uso general de datos y la contención de la red durante el inicio, ya que se difiere la carga de imágenes hasta el momento en que se necesitan realmente. Esto puede mejorar el tiempo de inicio y reducir el procesamiento en el subproceso principal, ya que se reduce el tiempo necesario para la decodificación de imágenes.

Sin embargo, la carga diferida es una técnica que puede afectar el LCP del procesamiento de imagen con contenido más grande de tu sitio web de manera negativa si la técnica te entusiasma demasiado. Debes evitar la carga diferida de las imágenes que se encuentran en el viewport durante el inicio.

Cuando uses cargadores diferidos basados en JavaScript, evita la carga diferida en imágenes en viewport, ya que estas soluciones suelen usar un atributo data-src o data-srcset como marcador de posición para los atributos src y srcset. El problema es que la carga de estas imágenes se retrasa porque el escáner de precarga del navegador no puede encontrarlas durante el inicio.

Incluso usar la carga diferida a nivel del navegador para cargar de forma diferida una imagen en el viewport puede fallar. Cuando se aplica loading="lazy" a una imagen en viewport, esa imagen se retrasará hasta que el navegador sepa que está en el viewport, lo que puede afectar el LCP de una página.

Nunca cargues de forma diferida las imágenes que se ven en el viewport durante el inicio. Es un patrón que afectará negativamente el LCP de tu sitio y, por lo tanto, la experiencia del usuario. Si necesitas una imagen durante el inicio, cárgala lo más rápido posible. Para hacerlo, no la cargues de forma diferida.

Carga diferida de bibliotecas

Deberías usar la carga diferida a nivel del navegador siempre que sea posible, pero si te encuentras en una situación en la que esa no es una opción (como un grupo significativo de usuarios que aún depende de navegadores más antiguos), se pueden usar las siguientes bibliotecas para cargar imágenes de forma diferida:

  • lazysizes es una biblioteca de carga diferida con todas las funciones que realiza esta carga de imágenes y iframes. El patrón que usa es bastante similar a los ejemplos de código que se muestran aquí, ya que se vincula automáticamente a una clase lazyload en elementos <img> y requiere que especifiques URLs de imagen en los atributos data-src o data-srcset, cuyo contenido se intercambia por atributos src o srcset, respectivamente. Usa Intersection Observaer (con el que puedes crear polyfills) y se puede extender con varios complementos para realizar acciones como cargar videos de forma diferida. Obtén más información sobre el uso de tamaños diferidos.
  • vanilla-lazyload es una opción liviana para la carga diferida de imágenes, imágenes de fondo, videos, iframes y secuencias de comandos. Aprovecha Intersection Observer, admite imágenes responsivas y permite la carga diferida a nivel del navegador.
  • lozad.js es otra opción liviana que solo usa Intersection Observer. Por lo tanto, es muy eficaz, pero necesitará tener polyfills para poder usarla en navegadores más antiguos.
  • Si necesitas una biblioteca de carga diferida específica de React, te recomendamos usar react-lazyload. Si bien no utiliza Intersection Observer, proporciona un método conocido de carga diferida de imágenes para los usuarios acostumbrados a desarrollar aplicaciones con React.