IntersectionObserver API y carga diferida de imágenes

Usar la carga de imágenes diferidas para mejorar el rendimiento de los proyectos web es una técnica de optimización popular. La cuestión es que las imágenes son recursos "pesados" que están llenos de sitios web modernos. Ya hemos publicado algo sobre esto. Aquí puede leer acerca de lo que la carga diferida le dio al sitio web de Walmart y aprender a usar IntersectionObserver en los proyectos React. Aquí hay un artículo sobre la optimización de sitios estáticos. Aquí hay material reciente sobre la implementación de la carga diferida utilizando las herramientas del navegador.



Hoy traemos a su atención una traducción de un artículo en el que se describe el uso de la API IntersectionObserver utilizando una página web simple como ejemplo. Este material está destinado a programadores principiantes.

¿Qué es la carga lenta de imágenes?


Al analizar el rendimiento de la aplicación, dos indicadores se destacan: el tiempo hasta la primera interactividad (Tiempo para interactuar) y el consumo de recursos (Consumo de recursos). Inevitablemente tendrán que enfrentarse a quienes se dedican al desarrollo web. Además, pueden surgir problemas con las aplicaciones no solo porque tardan mucho tiempo en prepararse para el trabajo o porque consumen demasiados recursos. Pero, en cualquier caso, es muy importante encontrar las fuentes de estos problemas lo antes posible y esforzarse por garantizar que los problemas ni siquiera surjan.

El uso de tecnologías de carga de imágenes diferidas en aplicaciones web minimiza el riesgo de problemas de rendimiento. Es decir, si analizamos la carga diferida desde el punto de vista de su influencia en el momento de poner la página en condiciones de trabajo y el consumo de recursos, obtenemos lo siguiente:

  1. Tiempo para la primera interactividad. Este es el momento en que la aplicación web necesita cargarse y llevar la interfaz a un estado adecuado para que el usuario pueda trabajar con ella. La carga diferida (además, estamos hablando no solo de imágenes) optimiza el tiempo de respuesta de las aplicaciones gracias a la tecnología de separar el código y cargar solo lo que se necesita para una página en particular, o lo que se necesita en un momento específico en el tiempo.
  2. Consumo de recursos. Los humanos son seres impacientes. Si un sitio web necesita más de 3 segundos para cargarse, el 70% de los usuarios abandonan ese sitio. Las aplicaciones web no deberían cargar tanto tiempo. La carga diferida le permite reducir la cantidad de recursos necesarios para operar las páginas. Esto, por ejemplo, puede significar que el código de un determinado proyecto se divide en fragmentos que se cargan solo en las páginas que los necesitan. Como resultado, el rendimiento del sitio aumenta y el consumo de recursos del sistema disminuye.

Por lo tanto, las tecnologías de carga lenta aceleran las aplicaciones, reduciendo el tiempo que se descargan y abren. Esto se logra debido al hecho de que los recursos se cargan solo cuando se necesitan.

Aquí hay un par de ventajas que la carga diferida brinda a los proyectos web:

  • Las páginas se cargan más rápido y se ponen en funcionamiento.
  • En la carga inicial de páginas, debe transferir y procesar menos datos.

Imágenes de carga diferida utilizando IntersectionObserver API


Si piensa en cómo se cargan las páginas web, queda claro que no tiene sentido descargar imágenes u otros recursos que no son visibles para el usuario. Cuando se usa carga diferida, las imágenes que están en el área visible de la página se cargan primero. Luego, a medida que el usuario se desplaza por la página, se cargan otras imágenes que caen en el área visible de la página.

Considera un ejemplo.


Página y su área visible

Echa un vistazo a la figura anterior. Aquí puede ver el navegador y la página web cargados en él. Las imágenes #IMG_1 y #IMG_2 están en el alcance de la página. Esto significa que son visibles para el usuario y están dentro de los límites del área de la ventana del navegador que se muestra en la pantalla.

Este orden de trabajo con la página no puede considerarse ideal cuando, cuando se carga, las imágenes #IMG_1 , #IMG_2 , #IMG_3 y #IMG_4 . Solo #IMG_1 y #IMG_2 son visibles para el #IMG_2 , y #IMG_3 y #IMG_4 están ocultos para él. Si carga la primera y la segunda imagen mientras carga la página, pero no descarga la tercera y cuarta imagen, esto podría tener un impacto positivo en el rendimiento del sitio. A saber, estamos hablando de lo siguiente. Cuando el usuario desplaza la página para que la tercera imagen sea visible, se carga. Si el desplazamiento continúa y la cuarta imagen se hace visible, también se carga.


Desplazarse por una página y cargar imágenes

Ahora que hemos descubierto exactamente cómo queremos trabajar con las imágenes, nos preguntamos cómo descubrir que cierto elemento de una página cae en su área visible. Los navegadores modernos tienen una API que le permite al programador saber cuándo un área determinada de la página se hace visible. Se trata de la API IntersectionObserver. Permite, mediante mecanismos asincrónicos, organizar el monitoreo de elementos y recibir notificaciones en los casos en que un elemento cruza el alcance del documento o cruza el borde de otro elemento.

Para configurar la carga diferida de imágenes, primero debemos preparar un código de plantilla para el elemento que se utilizará para describir las imágenes. Aquí esta:

 <img class="lzy_img" src="lazy_img.jpg" data-src="real_img.jpg" /> 

La clase le permite identificar un elemento como una imagen a la que se aplicará la carga diferida. El atributo src permite mostrar una imagen de marcador de posición antes de mostrar la imagen real. data-src almacena la dirección de la imagen real, que se cargará cuando el elemento ingrese al área visible de la página.

Ahora escribamos el código que implementa la carga diferida. Como ya se mencionó, utilizaremos la API IntersectionObserver para detectar el momento en que la imagen ingresa al área de visualización de la página.

Primero, cree una instancia de IntersectionObserver :

 const imageObserver = new IntersectionObserver(...); 

El constructor IntersectionObserver acepta una función con dos parámetros. Uno de ellos almacena una matriz que consta de elementos que necesitan ser monitoreados, el otro una instancia de IntersectionObserver . Se ve así:

 const imageObserver = new IntersectionObserver((entries, imgObserver) => {   entries.forEach((entry) => {       //...   }) }); 

El código de función verifica si las imágenes representadas por las entries matriz de entries cruzan con la ventana gráfica. Si este es el caso, entonces el atributo src de la imagen correspondiente registra lo que estaba en su atributo data-src .

 const imageObserver = new IntersectionObserver((entries, imgObserver) => {   entries.forEach((entry) => {       if(entry.isIntersecting) {           const lazyImage = entry.target           lazyImage.src = lazyImage.dataset.src       }   }) }); 

Aquí verificamos, usando la condición if(entry.isIntersecting) {...} , si el elemento cruza el área de visualización del navegador. Si es así, guardamos el elemento img en la constante lazyImage . Luego escribimos en su atributo src lo que estaba en su atributo data-src . Gracias a esto, la imagen cuya dirección se almacena en data-src se carga y se muestra. En el navegador, parece reemplazar la imagen del marcador de posición, lazy_img.jpg , con una imagen real.

Ahora necesitamos usar el método .observe() de nuestra instancia IntersectionObserver para comenzar a monitorear los elementos que nos interesan:

 imageObserver.observe(document.querySelectorAll('img.lzy_img')); 

Aquí seleccionamos todos los elementos img con la clase lzy_img document.querySelectorAll('img.lzy_img') con el comando document.querySelectorAll('img.lzy_img') y los pasamos al método .observe() . Habiendo recibido una lista de elementos, comienza el proceso de observarlos.

Para probar este ejemplo, comenzamos inicializando el proyecto Node.js:

 mkdir lzy_img cd lzy_img npm init -y 

Ahora cree el archivo lzy_img en la carpeta lzy_img :

 touch index.html 

Agregue el siguiente código:

 <html> <title>Lazy Load Images</title> <body>   <div>       <div style="">           <img class="lzy_img" src="lazy_img.jpg" data-src="img_1.jpg" />           <hr />       </div>       <div style="">           <img class="lzy_img" src="lazy_img.jpg" data-src="img_2.jpg" />           <hr />       </div>       <div style="">           <img class="lzy_img" src="lazy_img.jpg" data-src="img_3.jpg" />           <hr />       </div>       <div style="">           <img class="lzy_img" src="lazy_img.jpg" data-src="img_4.jpg" />           <hr />       </div>       <div style="">           <img class="lzy_img" src="lazy_img.jpg" data-src="img_5.jpg" />           <hr />       </div>   </div>   <script>       document.addEventListener("DOMContentLoaded", function() {           const imageObserver = new IntersectionObserver((entries, imgObserver) => {               entries.forEach((entry) => {                   if (entry.isIntersecting) {                       const lazyImage = entry.target                       console.log("lazy loading ", lazyImage)                       lazyImage.src = lazyImage.dataset.src                   }               })           });           const arr = document.querySelectorAll('img.lzy_img')           arr.forEach((v) => {               imageObserver.observe(v);           })       })   </script> </body> </html> 

Puede notar que aquí se describen 5 imágenes, en las que se muestra el marcador de posición lazy_img.jpg al cargar. El atributo data-src de cada uno de ellos contiene información sobre imágenes reales. Aquí hay una lista de nombres de imágenes utilizados en el proyecto:

 lazy_img.jpg img_1.jpg img_2.jpg img_3.jpg img_4.jpg img_5.jpg 

Debe crear todas estas imágenes usted mismo llevando la carpeta del proyecto a la vista que se muestra en la siguiente figura.


Carpeta de proyectos

En mi caso, el archivo lazy_img.jpg preparó con Windows Paint y las imágenes reales ( img_*.jpg Jpg ) se tomaron de pixabay.com .

Tenga en cuenta que la devolución de llamada utilizada para crear la instancia de IntersectionObserver tiene una llamada console.log() . Esto nos permitirá aprender sobre cómo realizar operaciones para cargar diferentes imágenes.

Ahora, para servir index.html , usamos el paquete http-server :

 npm i http-server 

Agregue la propiedad de start a la sección de scripts del archivo package.json :

 "scripts": {   "start": "http-server ./" } 

Después de eso, ejecute el npm run start en la terminal.

Ahora abra el navegador y vaya a la dirección 127.0.0.1:8080 . Nuestra página index.html al principio se parecerá a la siguiente figura.


Página sin imágenes reales

Puede notar que donde deberían estar las imágenes reales, ahora solo se muestran marcadores de posición. Aquí procedemos de la suposición de que la primera imagen está en el área de visualización, por lo que el navegador comienza a descargarla. Como resultado, la página ahora se verá así.


Navegador subió la primera imagen

Otras imágenes aún no cargadas. Todavía no han llegado al área de visualización.

Si se desplaza por la página mientras mira la consola, notará un problema. Consiste en el hecho de que al desplazarse por la misma imagen en la ventana de visualización, el navegador intenta descargarla varias veces.


Múltiples cargas de la misma imagen.

Esto no es en absoluto lo que necesitamos. Esto conduce a un desperdicio innecesario de recursos del sistema y degrada el rendimiento del proyecto.

Para resolver este problema, necesitamos eliminar el elemento img , cuya imagen real ya está cargada, de la lista de elementos que está observando nuestra instancia de IntersectionObserver . También necesitamos eliminar la clase lzy_img de este elemento. Esto es lo que parece en el código de función de devolución de llamada editado:

 <script>   document.addEventListener("DOMContentLoaded", function() {       const imageObserver = new IntersectionObserver((entries, imgObserver) => {           entries.forEach((entry) => {               if (entry.isIntersecting) {                   const lazyImage = entry.target                   console.log("lazy loading ", lazyImage)                   lazyImage.src = lazyImage.dataset.src                   lazyImage.classList.remove("lzy_img");                   imgObserver.unobserve(lazyImage);               }           })       });       const arr = document.querySelectorAll('img.lzy_img')       arr.forEach((v) => {           imageObserver.observe(v);       })   }) </script> 

Después de cargar la imagen real del elemento img correspondiente, su clase lzy_img se lzy_img y el elemento se excluye de la lista de elementos que IntersectionObserver está observando.

Aquí está el código de muestra:

 <html> <title>Lazy Load Images</title> <body>   <div>       <div style="">           <img class="lzy_img" src="lazy_img.jpg" data-src="img_1.jpg" />           <hr />       </div>       <div style="">           <img class="lzy_img" src="lazy_img.jpg" data-src="img_2.jpg" />           <hr />       </div>       <div style="">           <img class="lzy_img" src="lazy_img.jpg" data-src="img_3.jpg" />           <hr />       </div>       <div style="">           <img class="lzy_img" src="lazy_img.jpg" data-src="img_4.jpg" />           <hr />       </div>       <div style="">           <img class="lzy_img" src="lazy_img.jpg" data-src="img_5.jpg" />           <hr />       </div>   </div>   <script>       document.addEventListener("DOMContentLoaded", function() {           const imageObserver = new IntersectionObserver((entries, imgObserver) => {               entries.forEach((entry) => {                   if (entry.isIntersecting) {                       const lazyImage = entry.target                       console.log("lazy loading ", lazyImage)                       lazyImage.src = lazyImage.dataset.src                       lazyImage.classList.remove("lzy_img");                       imgObserver.unobserve(lazyImage);                   }               })           });           const arr = document.querySelectorAll('img.lzy_img')           arr.forEach((v) => {               imageObserver.observe(v);           })       })   </script> </body> </html> 

Resumen


Examinamos el ejemplo más simple de la implementación del sistema de carga diferida de imágenes. Pero IntersectionObserver le permite crear esquemas de carga bastante complejos y perezosos. Si está interesado en este tema, le recomendamos que consulte las publicaciones mencionadas al comienzo del material y lea la documentación .

Estimados lectores! ¿Tiene algún ejemplo de mejorar el rendimiento del sitio web después de implementar un sistema de carga de imágenes diferidas?

Source: https://habr.com/ru/post/453586/


All Articles