Prácticas recomendadas sobre la carga diferida

Si bien la carga diferida de imágenes y videos tiene beneficios de rendimiento positivos y medibles, no es una tarea para tomar a la ligera. Si te equivocas, podría haber consecuencias imprevistas. Por lo tanto, es importante tener en cuenta las siguientes inquietudes.

Mira lo que hay en la mitad superior

Puede resultar tentador realizar una carga diferida de todos los recursos multimedia de la página con JavaScript, pero es necesario resistir la tentación. No se debe cargar de forma diferida ningún elemento que se encuentre en la parte superior de la página. Estos recursos deben considerarse activos críticos y, por lo tanto, deben cargarse de forma habitual.

La carga diferida retrasa la carga de recursos hasta después de que el DOM es interactivo, cuando las secuencias de comandos terminan de cargarse y comienzan a ejecutarse. Esto está bien para las imágenes de la mitad inferior de la página, pero los recursos críticos de la mitad superior de la página deben cargarse con el elemento <img> estándar para que se muestren lo antes posible.

Por supuesto, la ubicación de la línea de plegado no es tan clara en estos días cuando los sitios web se visualizan en tantas pantallas de diferentes tamaños. En los dispositivos móviles, lo que se encuentra en la mitad superior de la página en una laptop puede estar debajo. No hay consejos a prueba de balas para abordar esto de manera óptima en cada situación. Deberás realizar un inventario de los elementos fundamentales de la página y cargar esas imágenes de la manera típica.

Además, es posible que no quieras ser tan estricto con la línea de plegado como el umbral para activar la carga diferida. Quizás, lo ideal para tu propósito sea establecer una zona de búfer un poco más abajo de la mitad de la página, de modo que las imágenes comiencen a cargarse mucho antes de que el usuario las desplace hacia el viewport. Por ejemplo, la API de Intersection Observer te permite especificar una propiedad rootMargin en un objeto de opciones cuando creas una nueva instancia IntersectionObserver. De esta manera, se proporciona un búfer a los elementos de manera efectiva, lo que activa el comportamiento de carga diferida antes de que los elementos estén en el viewport:

let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
  // lazy-loading image code goes here
}, {
  rootMargin: "0px 0px 256px 0px"
});

Si el valor de rootMargin es similar a los valores que especificarías para una propiedad margin de CSS, es porque lo es. En este caso, el margen inferior del elemento observado (el viewport del navegador de forma predeterminada, pero esto se puede cambiar a un elemento específico mediante la propiedad root) se amplía a 256 píxeles. Eso significa que se ejecutará la función de devolución de llamada cuando un elemento de imagen esté dentro de los 256 píxeles del viewport y la imagen comenzará a cargarse antes de que el usuario la vea.

Para lograr este mismo efecto en navegadores que no admiten Intersection Observe, usa el código de control de eventos de desplazamiento y ajusta la verificación de getBoundingClientRect para que incluya un búfer.

Cambio de diseño y marcadores de posición

La carga diferida de medios puede provocar cambios en el diseño si no se utilizan marcadores de posición. Estos cambios pueden desorientar a los usuarios y activar operaciones costosas de diseño del DOM que consumen recursos del sistema y contribuyen a los bloqueos. Como mínimo, considera usar un marcador de posición de color sólido que ocupe las mismas dimensiones que la imagen de destino o técnicas como LQIP o SQIP que sugieran el contenido de un elemento multimedia antes de que se cargue.

En el caso de las etiquetas <img>, src debe apuntar inicialmente a un marcador de posición hasta que ese atributo se actualice con la URL de la imagen final. Usa el atributo poster en un elemento <video> para apuntar a una imagen de marcador de posición. Además, usa los atributos width y height en las etiquetas <img> y <video>. Esto garantiza que la transición de los marcadores de posición a las imágenes finales no cambie el tamaño renderizado del elemento a medida que se carga el contenido multimedia.

Demoras en la decodificación de imágenes

Cargar imágenes grandes en JavaScript y soltarlas en el DOM puede afectar el subproceso principal y hacer que la interfaz de usuario no responda durante un breve período mientras se produce la decodificación. La decodificación asíncrona de imágenes con el método decode antes de insertarlas en el DOM puede reducir este tipo de bloqueos, pero ten en cuenta que aún no está disponible en todas partes y agrega complejidad a la lógica de carga diferida. Si quieres usarlo, tendrás que revisarlo. A continuación, se muestra cómo puedes usar Image.decode() con un resguardo:

var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

if ("decode" in newImage) {
  // Fancy decoding logic
  newImage.decode().then(function() {
    imageContainer.appendChild(newImage);
  });
} else {
  // Regular image load
  imageContainer.appendChild(newImage);
}

Consulta este vínculo de CodePen para ver un código similar a este ejemplo en acción. Si la mayoría de las imágenes son bastante pequeñas, es posible que esto no ofrezca gran utilidad, pero sin duda puede ayudar a reducir los bloqueos cuando se cargan de forma diferida imágenes grandes y se insertan en el DOM.

Cuando algo no se carga

A veces, los recursos multimedia no se cargan por algún motivo y se producen errores. ¿Cuándo podría ocurrir esto? Depende, pero hay una situación hipotética para ti: tienes una política de almacenamiento en caché HTML durante un período breve (p.ej., cinco minutos) y el usuario visita el sitio o un usuario deja abierta una pestaña inactiva durante un período prolongado (p.ej., varias horas) y regresa para leer tu contenido. En algún punto del proceso, se produce una reimplementación. Durante esta implementación, el nombre de un recurso de imagen cambia debido al control de versiones basado en hash o se quita por completo. Para cuando el usuario realiza una carga diferida de la imagen, el recurso ya no está disponible y, por lo tanto, falla.

Si bien estos casos son relativamente poco frecuentes, puede ser conveniente tener un plan de copia de seguridad si la carga diferida falla. En el caso de las imágenes, esta solución puede ser similar a la siguiente:

var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

newImage.onerror = function(){
  // Decide what to do on error
};
newImage.onload = function(){
  // Load the image
};

Lo que decidas hacer en caso de que se produzca un error dependerá de tu aplicación. Por ejemplo, podrías reemplazar el área del marcador de posición de la imagen por un botón que permita al usuario intentar cargar la imagen de nuevo o simplemente mostrar un mensaje de error en el área del marcador de posición de la imagen.

También podrían surgir otras situaciones. Hagas lo que hagas, nunca es mala idea indicar al usuario cuando se produce un error y, posiblemente, brindarle una acción para que realice si algo sale mal.

Disponibilidad de JavaScript

No se debe suponer que JavaScript siempre está disponible. Si vas a cargar imágenes de forma diferida, considera ofrecer lenguaje de marcado de <noscript> que muestre imágenes en caso de que JavaScript no esté disponible. El ejemplo de resguardo más simple posible implica usar elementos <noscript> para entregar imágenes si JavaScript está desactivado:

Soy una imagen.

Si JavaScript está desactivado, los usuarios verán tanto la imagen del marcador de posición como la imagen contenida en los elementos <noscript>. Para solucionar este problema, coloca una clase de no-js en la etiqueta <html> de la siguiente manera:

<html class="no-js">

Luego, coloca una línea de secuencia de comandos intercalada en <head> antes de que se soliciten hojas de estilo a través de las etiquetas <link> para quitar la clase no-js del elemento <html> si JavaScript está activado:

<script>document.documentElement.classList.remove("no-js");</script>

Por último, usa CSS para ocultar elementos con una clase diferida cuando JavaScript no está disponible:

.no-js .lazy {
  display: none;
}

Esto no evita que se carguen las imágenes de marcador de posición, pero el resultado es más conveniente. Las personas con JavaScript desactivado obtienen algo más que imágenes de marcador de posición, lo que es mejor que marcadores de posición sin contenido de imagen significativo.