Introducción
Veamos en este pequeño tutorial cómo hacer lazy loading de imágenes con Intersection Observer API.
Así evitaremos cargar todas las páginas, sobrecargar nuestros servidores y gastar ancho de banda.
Probar página terminada
Puedes probar aquí la página terminada. Te recomiendo que abras la consola de depuración para ver que las imágenes se cargan conforme navegas.
¿Qué es el lazy loading?
El lazy loading es cargar las imágenes de una página web conforme el usuario las necesite.
Es decir, si tenemos una página con 100 imágenes el usuario no verá las 100 al mismo momento, sino que tal vez irá bajando poco a poco. Justo ahí entra en acción el observador de intersección, que nos dirá si el usuario está a punto de ver la imagen. En caso de que sí, la cargamos.
¿Qué es intersection observer API?
Es una nueva API de programación de JavaScript que nos dice si la pantalla visible del usuario está a punto de intersecar con algo.
Tiene múltiples usos, pero veremos cómo hacerlo para cargar imágenes conforme el usuario hace scroll.
Hace tiempo, cuando esta API no existía, no tengo idea de cómo se hacía pero supongo que se vigilaba el scroll, la altura del documento y muchísimas cosas más.
Pues Intersection observer viene a solucionarnos este problema. Ah, por cierto, recordemos que necesitamos un navegador actualizado.
Actualmente es compatible con Edge 12, Firefox y Chrome en sus últimas versiones. Puedes ver la lista aquí.
Ventajas
Como lo dije, ahorramos ancho de banda porque no requerimos todas las imágenes al mismo tiempo sin que el usuario las vaya a ver.
Y por otro lado, la página cargará más rápido.
Pero bueno, vamos allá.
Lazy loading de imágenes con Intersection Observer API
Lo que haremos será una simple página con texto e imágenes. Y las imágenes serán cargadas conforme el usuario haga scroll o navegue.
Documento HTML
Entonces tenemos nuestro documento HTML que queda así:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Lazy Loading de imágenes con Intersection Observer API</title>
</head>
<style>
h1{
font-size: 3em;
}
p{
font-size: 1.4em;
}
</style>
<body>
<!-- Este div se repetirá en el verdadero proyecto, con imágenes distintas -->
<div>
<h1>Este es el título</h1>
<p>
<span>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Obcaecati similique illo amet natus, atque doloremque quos sunt officia eligendi, officiis fugiat possimus inventore. Dolor itaque, libero, delectus tenetur doloremque earum.</span>
<span>Commodi dolor vel labore neque maiores, odio doloremque similique sequi modi. Quod cumque, sunt, vero dignissimos debitis facere reiciendis tenetur fugit modi, placeat nostrum iure sint assumenda. Temporibus, minus, alias!</span>
<span>Blanditiis quo, adipisci consequuntur beatae fugiat rerum, optio? Dignissimos porro at nihil cupiditate, libero dicta ratione. Provident, qui expedita iure. Sunt temporibus quae dignissimos, sed saepe numquam modi ipsa dicta.</span>
<span>Nobis nam sequi, maiores est vitae soluta at, suscipit molestiae enim a! Consequatur, ipsam. Nesciunt ea ullam neque illum, veritatis laborum eos laudantium ipsum necessitatibus nostrum quae quod omnis eum!</span>
<span>Qui iusto eum similique distinctio nihil aut odio quibusdam tempora tempore beatae ad perferendis, natus, illum praesentium iste placeat mollitia sed deserunt totam autem ab suscipit, ea obcaecati. Eveniet, sed.</span>
<br>
<span>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Obcaecati similique illo amet natus, atque doloremque quos sunt officia eligendi, officiis fugiat possimus inventore. Dolor itaque, libero, delectus tenetur doloremque earum.</span>
<span>Commodi dolor vel labore neque maiores, odio doloremque similique sequi modi. Quod cumque, sunt, vero dignissimos debitis facere reiciendis tenetur fugit modi, placeat nostrum iure sint assumenda. Temporibus, minus, alias!</span>
<span>Blanditiis quo, adipisci consequuntur beatae fugiat rerum, optio? Dignissimos porro at nihil cupiditate, libero dicta ratione. Provident, qui expedita iure. Sunt temporibus quae dignissimos, sed saepe numquam modi ipsa dicta.</span>
<span>Nobis nam sequi, maiores est vitae soluta at, suscipit molestiae enim a! Consequatur, ipsam. Nesciunt ea ullam neque illum, veritatis laborum eos laudantium ipsum necessitatibus nostrum quae quod omnis eum!</span>
<span>Qui iusto eum similique distinctio nihil aut odio quibusdam tempora tempore beatae ad perferendis, natus, illum praesentium iste placeat mollitia sed deserunt totam autem ab suscipit, ea obcaecati. Eveniet, sed.</span>
<br>
</p>
<img class="lazy-loading" src="" data-src="https://picsum.photos/200" alt="Una imagen">
</div>
</body>
</html>
No prestemos atención a otra cosa que la imagen. Vemos que no tiene src
, sino data-src
. Esto es porque no queremos que el navegador descargue la imagen, dejamos vacío el atributo y ponemos la verdadera imagen en un atributo que el navegador no conoce; bueno, sí lo conoce pero no descargará la imagen de ahí.
Por otro lado vemos que la imagen tiene la clase lazy-loading. Esto es para que luego podamos recuperarlas a todas con querySelectorAll
.
Es importante mencionar que vamos a repetir muchas veces el div
. En el código de arriba no se repite porque sería muy largo pegarlo aquí en el post, pero vamos a repetirlo para que haya muchas imágenes y scroll.
Se darán cuenta de que lo cargamos de https://picsum.photos/200 (un servicio que da imágenes para probar). Pues podemos cambiar el 200 por otro número y ese será el tamaño de la imagen en pixeles.
Adicionalmente podemos anexar otro número después del 200 (por ejemplo 200/100) para la otra medida.
Recuperar imágenes
Ahora vamos a recuperar todas las imágenes que tengan la clase lazy-loading
. Para ello esperaremos el evento DOMContentLoaded
que es como el document ready
de jQuery.
Más tarde usamos document.querySelector
y le pasamos el selector de img
que tenga la clase lazy-loading
(img.lazy-loading)
document.addEventListener("DOMContentLoaded", function () {
var $imagenes = document.querySelectorAll("img.lazy-loading");
});
Te preguntarás por qué pongo un $
antes del nombre de la variable. Y es porque tengo la costumbre de que a los elementos del DOM les pongo dicho símbolo para identificarlos como elementos, no como variables que uso en el programa.
Es una práctica mía, no importa el nombre
Comprobar IntersectionObserver y observar
Ahora comprobamos si el navegador tiene soporte para observar la intersección. Y en caso de que sí, creamos un observador y por cada entrada que tenga añadimos un callback.
Dicho callback es llamado cuando hay una intersección con un elemento.
Comparamos si el ratio es mayor a 0 (lo que significa que el usuario va a ver la imagen) y a la imagen le ponemos el atributo src
copiando lo que había en data-src
:
if ("undefined" !== typeof IntersectionObserver) {
var observador = new IntersectionObserver(function (entradas) {
for (var i = 0; i < entradas.length; entradas++) {
var entrada = entradas[i];
if (entrada.intersectionRatio > 0) {
var imagen = entrada.target;
imagen.src = imagen.dataset.src;
observador.unobserve(imagen);
}
}
});
} else {
for (var j = 0; j < $imagenes.length; j++) {
$imagenes[j].src = $imagenes[j].dataset.src;
}
}
Por cierto, en caso de que no exista la API en el navegador entonces cargamos la fuente de la imagen normalmente; es decir, no hay lazy-loading pero las imágenes sí se verán (aunque cargarán de modo convencional)
Finalmente dejamos de observar a la imagen con:
observador.unobserve(imagen);
Porque una vez cargada la imagen ya no necesitaremos vigilarla.
Observar imágenes
Ya programamos el callback de la imagen y dijimos que la imagen será cargada y dejada de observar. Lo único que falta ahora es observarlas para que dicho callback sea llamado:
for (var x = 0; x < $imagenes.length; x++) {
observador.observe($imagenes[x]);
}
Con eso vamos a observar cada imagen.
Código completo
El código completo de ejemplo queda así:
document.addEventListener("DOMContentLoaded", function () {
var $imagenes = document.querySelectorAll("img.lazy-loading");
if ("undefined" !== typeof IntersectionObserver) {
var observador = new IntersectionObserver(function (entradas) {
for (var i = 0; i < entradas.length; entradas++) {
var entrada = entradas[i];
if (entrada.intersectionRatio > 0) {
var imagen = entrada.target;
imagen.src = imagen.dataset.src;//src = data-src
console.log("Cargada: ", imagen.src)
observador.unobserve(imagen);
}
}
});
for (var x = 0; x < $imagenes.length; x++) {
observador.observe($imagenes[x]);
}
} else {
//En caso de que no exista la API
for (var j = 0; j < $imagenes.length; j++) {
$imagenes[j].src = $imagenes[j].dataset.src;
}
}
});
Y con eso, cualquier imagen que tenga la clase será cargada con lazy-loading.
Probar documento
Si ahora abrimos nuestro documento, abrimos la consola y navegamos, veremos que la imagen se irá cargando conforme hacemos scroll o mejor dicho, mientras navegamos ya sea con el teclado, el touch o el mouse.
Recuerda que puedes probar el proyecto terminado en mi página web.
Así es como podemos hacer lazy loading de imágenes con JavaScript sin quebrarnos la cabeza.
Puedes también, en la consola, abrir la pestaña de Network y ver que las imágenes no se cargan al mismo tiempo.
Muy interesante tu código, no tienes uno que funcione con srcset ? he visto varios pero todos muy enredados
Me parece que no es recomendable usar srcset con JavaScript. Lo recomendable sería usar algo así como el lazy loading pero detectar el tamaño de la pantalla del dispositivo y a partir de ello cargar una imagen con el tamaño apropiado
Pingback: He renovado mi página principal y ahora es open source - Parzibyte's blog