Implementación de Lazy Loading con Intersection Observer

Resumen

Cargar todas las imágenes de una aplicación al mismo tiempo encarece la primera visita y degrada la experiencia. Con lazy loading usando IntersectionObserver puedes mostrar solo las imágenes visibles en el viewport, aligerar la carga inicial y aplicar el patrón de forma selectiva por sección.

¿Cómo crear el observador con IntersectionObserver?

La idea es centralizar un único observador reutilizable para todas las imágenes del DOM, sin pasar opciones extra, para mantener el código simple.

Dentro del archivo main.js, antes de las funciones de manipulación del DOM, defines el observador como un util perezoso. El nombre lazyLoader comunica mejor su propósito que llamarlo simplemente observer [02:00].

js const lazyLoader = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { const url = entry.target.getAttribute('data-img'); entry.target.setAttribute('src', url); } }); });

El callback recibe dos parámetros: entries y observer. En este caso solo necesitas el primero, porque la llamada a lazyLoader.observe() la haces fuera de la función [02:30].

¿Qué hace IntersectionObserver? Vigila elementos del DOM y dispara un callback cuando entran o salen del viewport, lo que te permite reaccionar solo a lo que el usuario realmente ve.

¿Por qué cambiar src por un atributo data-img?

Si dejas la URL en src desde el inicio, el navegador descarga la imagen aunque no esté visible. La técnica consiste en guardar la URL en un atributo personalizado y moverla a src solo cuando la imagen entra al viewport [04:00].

Dentro de createMovies modificas la asignación:

  • Antes: movieImage.setAttribute('src', urlImagen).
  • Ahora: movieImage.setAttribute('data-img', urlImagen).
  • Después agregas la imagen al observador con lazyLoader.observe(movieImage).

Al recargar la página, el alt aparece visible porque el src aún no está. Eso confirma que la práctica de accesibilidad con texto alternativo funciona y que el navegador todavía no descarga la imagen [05:30].

¿Cómo acceder al elemento HTML real desde una entry?

Un detalle clave: cada entry no es el nodo HTML directamente, es una instancia con propiedades como isIntersecting, isVisible y target. El elemento real está en entry.target [08:00].

Por eso, métodos como getAttribute y setAttribute no existen en entry, sino en entry.target. La lógica final queda así:

  • Lees la URL con entry.target.getAttribute('data-img').
  • La inyectas con entry.target.setAttribute('src', url).
  • Envuelves todo en un if (entry.isIntersecting) para activar la carga solo cuando la imagen sea visible.

¿Qué es isIntersecting? Es una propiedad booleana de cada entry que indica si el elemento observado está actualmente dentro del viewport o del contenedor configurado.

¿Cómo activar lazy loading solo en secciones específicas?

Aplicar la carga perezosa en toda la aplicación puede ser excesivo. En secciones como trending preview tiene sentido, pero en otras quizá quieras que las imágenes carguen de inmediato.

La solución es agregar un parámetro extra a createMovies que controle el comportamiento:

js function createMovies(movies, container, lazyLoad = false) { // ... if (lazyLoad) { movieImage.setAttribute('data-img', url); lazyLoader.observe(movieImage); } else { movieImage.setAttribute('src', url); } }

El parámetro lazyLoad es false por defecto, así que las llamadas existentes mantienen su comportamiento original. Solo donde quieras activar la optimización envías true, por ejemplo en getTrendingMoviesPreview [13:30].

Al recargar, la sección configurada con true muestra únicamente las tres primeras imágenes. El resto se carga conforme haces scroll, y puedes verificarlo en la pestaña Network del navegador, donde las peticiones aparecen escalonadas [14:00].

¿Qué errores comunes evitar al implementar lazy loading?

Hay un detalle de CSS que rompe todo el patrón sin que sea evidente. Si las imágenes tienen height: 0, el navegador las considera todas dentro del viewport simultáneamente y dispara la carga de golpe [15:30].

Puntos a cuidar:

  • Define un height o min-height para los contenedores de imagen.
  • Verifica en Network que las peticiones se hagan de forma incremental.
  • Recuerda que cada entry tiene target; nunca llames métodos del DOM directamente sobre la entry.

Este patrón aligera la primera carga, mejora métricas de rendimiento y deja preparado el terreno para manejar imágenes rotas o null desde la API. ¿Tú lo resolviste igual o usaste otro enfoque? Cuéntalo en los comentarios.