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 una carga diferida en 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 de carga diferida que se pueden usar en combinación para obtener la mejor compatibilidad con los navegadores:

Usa 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 los elementos <img> y también a los 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 del navegador. Si el navegador no admite la carga diferida, se ignorará el atributo y las imágenes se cargarán de inmediato, como siempre.

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

Para obtener más información, consulta Carga diferida a nivel del navegador para la Web.

Cómo usar Intersection Observer

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

Si ya escribiste código de carga diferida, es posible que hayas completado la 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 forma 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 se basa en varios controladores de eventos, ya que solo debes registrar un observador para que observe los elementos en lugar de escribir un código de detección de visibilidad de elementos tedioso. Todo lo 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 tus elementos <img> de carga 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 se cargue la página 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 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 el DOM para todos los elementos <img> con una clase de lazy. Si Intersection Observer está disponible, crea un observador nuevo que ejecute una devolución de llamada cuando los elementos img.lazy ingresen al viewport.

Intersection Observer está disponible en todos los navegadores actualizados. 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 a través de 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 de CSS, por lo que debes considerar otros métodos si tienes imágenes de fondo para la carga 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 documento y de objetos 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 una regla de CSS que implica que un recurso externo no se aplica al documento en su construcción actual, el navegador no la solicita.

Este comportamiento especulativo se puede usar para diferir la carga de imágenes en CSS mediante el uso de JavaScript para determinar cuándo un elemento está dentro del viewport y, luego, aplicar una clase a ese elemento que invoque un estilo invocando una imagen de fondo. Esto hace que la imagen se descargue en el momento en que se necesita, en lugar de en la carga inicial. Por ejemplo, tomemos un elemento que contiene una imagen de fondo hero grande:

<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>

Normalmente, el elemento div.lazy-background contendría 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 */
}

Desde 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 excelente optimización que reduce el uso general de datos y la contención de red durante el inicio aplazando la carga de las imágenes hasta cuando sean realmente necesarias. Esto puede mejorar el tiempo de inicio y reducir el procesamiento en el subproceso principal, ya que reduce el tiempo necesario para las decodificación de imágenes.

Sin embargo, la carga diferida es una técnica que puede afectar de forma negativa el LCP de pintura con contenido más grande de tu sitio web si te interesa mucho la técnica. Una cosa que debes evitar es la carga diferida de imágenes que se encuentran en el viewport durante el inicio.

Cuando uses cargadores diferidos basados en JavaScript, evita la carga diferida de imágenes en el 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 se retrasará la carga de estas imágenes porque el análisis de precarga del navegador no puede encontrarlas durante el inicio.

Incluso la carga diferida a nivel del navegador para la carga diferida de una imagen en el viewport puede tener consecuencias negativas. Cuando se aplica loading="lazy" a una imagen en el viewport, esa imagen se retrasa hasta que el navegador sabe con seguridad que está en el viewport, lo que puede afectar el LCP de una página.

Nunca cargues de forma diferida las imágenes que sean visibles en el viewport durante el inicio. Es un patrón que afectará de forma negativa al LCP de tu sitio y, por lo tanto, a la experiencia del usuario. Si necesitas una imagen durante el inicio, cárgala lo más rápido posible sin una carga diferida.

Carga diferida de bibliotecas

Debes usar la carga diferida a nivel del navegador siempre que sea posible. Sin embargo, si te encuentras en una situación en la que no es posible, como un grupo significativo de usuarios que todavía depende de navegadores más antiguos, puedes usar las siguientes bibliotecas para cargar imágenes de forma diferida:

  • lazysizes es una biblioteca de carga diferida con todas las funciones que carga de forma diferida 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 imágenes en los atributos data-src o data-srcset, cuyo contenido se intercambia por los atributos src o srcset, respectivamente. Usa Intersection Observer (que puedes aplicar con polyfill) y se puede extender con varios complementos para realizar acciones como videos de carga diferida. Obtenga más información sobre el uso de tamaños diferidos.
  • vanilla-lazyload es una opción ligera para imágenes de carga diferida, imágenes de fondo, videos, iframes y secuencias de comandos. Aprovecha Intersection Observer, admite imágenes responsivas y habilita la carga diferida a nivel del navegador.
  • lozad.js es otra opción liviana que solo usa Intersection Observer. Por lo tanto, tiene un alto rendimiento, pero necesita polyfill para poder usarla en navegadores más antiguos.
  • Si necesitas una biblioteca de carga diferida específica para React, considera usar react-lazyload. Si bien no usa Intersection Observer, proporciona un método conocido de imágenes de carga diferida para aquellos acostumbrados a desarrollar aplicaciones con React.