Qué se debe optimizar en el frontend (y qué no)

1

Optimización de Proyectos con APIs REST en JavaScript

2

Optimización de Consumo de API REST en Frontend

3

Uso del Inspector de Elementos y Network en Navegadores

Quiz: Qué se debe optimizar en el frontend (y qué no)

Optimización de imágenes

4

Estrategias de Pantallas de Carga: Spinners vs Skeletons

5

Implementación de Pantallas de Carga con CSS y HTML

6

Implementación de Intersection Observer para Lazy Loading en Imágenes

7

Implementación de Lazy Loading con Intersection Observer

8

Manejo de Errores y Lazy Loading en Imágenes de Aplicaciones Web

Quiz: Optimización de imágenes

Paginación

9

Comparación: Paginación vs. Scroll Infinito en Aplicaciones Web

10

Implementación de Botón para Cargar Más con Paginación API

11

Scroll Infinito en Aplicaciones Web: Implementación y Mejores Prácticas

12

Implementación de Límite en Infinite Scrolling con APIs

13

Implementación de Closures en Paginación Infinita con JavaScript

Quiz: Paginación

Almacenamiento local

14

Almacenamiento Local con Local Storage en JavaScript

15

Maquetación y Estilos para Sección de Películas Favoritas

16

Uso de LocalStorage para Guardar y Recuperar Datos en JavaScript

17

Gestión de Películas Favoritas con Local Storage en JavaScript

Quiz: Almacenamiento local

Bonus

18

Internacionalización y Localización en Aplicaciones Web

19

Despliegue Seguro de Aplicaciones Web y Protección de API Keys

Próximos pasos

20

Optimización de Aplicaciones Frontend con APIs REST y JavaScript

No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Implementación de Intersection Observer para Lazy Loading en Imágenes

6/20
Recursos

¿Cómo usar el Intersection Observer en JavaScript para implementar Lazy Loading?

En el mundo del desarrollo web moderno, maximizar el rendimiento y optimización de un sitio es esencial. Una de las técnicas más efectivas para mejorar la carga de los recursos es el Lazy Loading, que nos permite cargar los elementos, como imágenes, únicamente cuando son visibles en el viewport del usuario. Aquí es donde Intersection Observer se convierte en una herramienta invaluable. Su capacidad para detectar dinámicamente los cambios en la visibilidad de los elementos es vital para reducir la carga inicial de las páginas y mejorar la experiencia del usuario.

¿Qué es el Intersection Observer y cuál es su utilidad?

Intersection Observer es una herramienta nativa de JavaScript que permite observar cuando los elementos HTML entran o salen del viewport o de un contenedor específico. Esta habilidad es útil no solo para implementar lazy loading, sino también para otras interacciones que dependen de la visibilidad de los elementos en el documento.

Principales características de Intersection Observer:

  • Detección de cambios de visibilidad: Puedes monitorear cuando elementos HTML aparecen y desaparecen en el viewport.
  • Optimización del rendimiento: Carga imágenes y otros elementos solo cuando son necesarios.
  • Interactividad mejorada: Úsalo para ejecutar animaciones o cambiar el comportamiento de elementos dependiendo de su visibilidad.

¿Cómo definir un Intersection Observer?

Para comenzar a utilizar Intersection Observer, primero necesitas crear una instancia. El constructor requiere dos parámetros: un callback y unas opciones:

let observer = new IntersectionObserver(callback, options);

Callback: Esta función es llamada cada vez que uno de los elementos que estamos observando cambia su estado de visibilidad. Toma dos argumentos: entries, que son los elementos observados y observer, la instancia del observador.

Options: Permite definir el contenedor (root) donde estás observando los cambios. Sin definirlo explícitamente, el root por defecto es el documento entero.

Configurar el Callback

El callback es una función esencial en el uso de Intersection Observer, ya que maneja las entradas (entries) que definen los elementos que queremos observar.

let callback = (entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      let image = entry.target;
      // Cargar la imagen al entrar en la vista
      image.src = image.dataset.src;
      observer.unobserve(image);
    }
  });
};

¿Cómo observar elementos específicos?

Una vez que tienes tu IntersectionObserver definido, utiliza el método observe para monitorear elementos específicos en el DOM. Aquí, puedes observar imágenes por ejemplo.

let images = document.querySelectorAll('img[data-src]');
images.forEach(image => {
  observer.observe(image);
});

Implementación práctica en Lazy Loading

Al implementar Lazy Loading, el proceso involucra establecer la URL de las imágenes en un atributo distinto, por ejemplo data-src, luego cambiar al atributo src cuando la imagen está en el viewport.

  1. Definir datos de imagen: Usa data-src para guardar la URL inicial de la imagen.
  2. Monitorear imágenes: Usa Intersection Observer para vigilar cambios de visibilidad.
  3. Cargar imágenes: Cambia el atributo src desde data-src cuando la imagen se hace visible.

Este enfoque reduce la carga inicial de la página, mejorando el rendimiento del sitio web y ahorrando recursos, lo que es especialmente beneficioso para usuarios en dispositivos móviles o con conexiones lentas.

Consejos prácticos

  • Reutilizar observadores: Aunque puedes crear múltiples observadores para diferentes secciones, un solo observador global puede simplificar el manejo de recursos.
  • Desinstanciar después de uso: Ayuda a optimizar el rendimiento eliminando observadores cuando ya no son necesarios usando observer.unobserve(element).
  • Experiencia de usuario: Considerar el uso de loaders o indicadores de carga para mejorar la percepción de rapidez por parte del usuario.

Prosigue explorando estos conceptos y prueba distintas configuraciones para adaptar el Intersection Observer a tus necesidades. No temas experimentar, ¡cada intento es un paso hacia el dominio de esta herramienta!

Aportes 19

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Bueno, aquí va mi solución, la verdad batallé con dos cosas:

Donde diablos poner el observer y qué se le tenía que pasar y cómo cambiar la propiedad src y por ende cómo sacar la propiedad data-img.

Ok, primero todo éste código va en el archivo main.js, decidí crear una función createObserver cómo indica en la documentación de MDN, ya que puede ser que requiera poner otro observador después:

function createObserver() {
    return new IntersectionObserver((elements) => {
        elements.forEach(element => {
            if (element.isIntersecting)
                element.target.setAttribute(
                    'src',
                    element.target.dataset.img
                )
        })
    })
}

//creo el observer,.
let observer = createObserver()

Después en la función createMovies en lugar de cargar la propiedad src, cree una nueva llamada data-img:

movieImg.setAttribute(
            'data-img',
            'https://image.tmdb.org/t/p/w300/' + movie.poster_path
        )

En esa misma función, al crear el elemento de Imagen, lo meto al observer:

movieContainer.appendChild(movieImg)
container.appendChild(movieContainer)
observer.observe(movieImg)

y en el callback pregunto si lo estoy viendo con la propiedad “isIntersecting”. Si ésto es cierto, accedo al elemento con element.target y de ahí a la propiedad data-img con el “dataset.img”. Como se ve, todo lo que creemos con data-algo va a ir en el dataset.algo. Si quieren saber más, aquí..

Espero que mi solución sea parecida a la que veré en unos minutos. debo decir que me tardé bastante y si el profe tiene una solución mas sencilla dejo de estudiar por hoy.

Mi resumen:
.
Intersection Observer nos permite observar cambios. Es decir, cuando un elemento se intersecta con el wiport
Vamos a tener targets (elementos) y veremos cuando entran dentro del scroll. Luego, le diremos cuál de esos queremos observar. Así podremos mostrar, ocultar, cargar, etc.
.

  1. Tenemos que crear un observador → Una instancia del Intersection Observer
  2. Callback

.

Podemos crear observador por cada contenedor que muestra imágenes.
U observar el root. Esto haría que el observador esté más cargado, pero no tendríamos que hacer tantos observadores.
La primera opción puede aumentar la velocidad de carga. La recomendación del profe es hacer la segunda y si no estamos conforme con los resultados ir por la primera.
.

Más información en esta clase de Manipulación del DOM

Pues dure como 4 horas haciendo y tratando de entender lo mejor que puede, tengo un pequeño bug que no me funciona con #trends pero por ahora con el scroll horizontal funciona bien.

Tengo el código separado por modulos así que mi archivo lazy.js es el siguiente:

const loadImage = (entry) => {
    const imgNode = entry.target;
    const url = imgNode.dataset.src;
    imgNode.src = url;
    console.log(imgNode);

    observer.unobserve(imgNode);
}

const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            loadImage(entry);
        }   
    })
}, {
    root: null,
    rootMargin: '0px',
    threshold: 0.2
});


const registerImage = (images) => {
    observer.observe(images)
}


export default registerImage;  

en la función de createMovie() le agrege un registerImage y configurar un data.src

function createMovie(movies, container) {
    /*Clear html of page */
    container.innerHTML = "";

    movies.map(movie => {

        const movieContainer = document.createElement('div');
        movieContainer.classList.add('movie-container');
        //Agregamos evento para llevar a movieDetail
        movieContainer.addEventListener('click', () => {
            location.hash = '#movie=' + movie.id;
        })

        const movieImg = document.createElement('img');
        movieImg.classList.add('movie-img');
        movieImg.setAttribute('alt', movie.title);
        movieImg.dataset.src = `${IMG_URL}${movie.poster_path}`;
        registerImage(movieImg);
        movieContainer.appendChild(movieImg);
        container.appendChild(movieContainer);
    });
}

Este tema tambien se ve en el curso de manipulacion del Dom, ademas se puede usar el atributo loading=“lazy”

ejemplo <img src… loading=“lazy” />

solo hay que ver la compatibilidad de los navegadores en
can i use

interesante que al aplicar el intersection observer notaba un comportamiento raro para lo que sería las categorías, ya que siempre se cargaban todas las imágenes, sin embargo era porque el contenedor principal “.genericList-container” no tenía un alto definido, comencé a jugar con un min-height y al menos con unos 1000px me cargaban 8 imágenes y ya el resto se cargaba conforme se hacia el scroll, de repente hay alguien que resolvió esto de otra manera, por favor comenten para seguir aprendiendo.

imagina que tienes una cámara de seguridad en five night at freddys que vigila una puerta.

La cámara = observador,
el root = el área que cubre la cámara
la puerta = elemento a observar,
las opciones = reglas para activar la alarma (por ejemplo, si alguien cruza la puerta completamente o solo un poco),
El threshold = % de la puerta abierta necesario para que se active la alarma,
la función = la alarma que suena cuando se cumple la condición.

El Intersection Observer es una API de JavaScript que permite realizar un seguimiento de las intersecciones entre elementos del DOM y el viewport del navegador. Proporciona una forma eficiente de detectar cuando un elemento entra o sale del área visible del usuario.
.
La API del Intersection Observer es útil en situaciones donde necesitas saber si un elemento es visible en la pantalla o cuándo un elemento se encuentra dentro de un contenedor desplazable. En lugar de depender de eventos como el desplazamiento o el redimensionamiento de la ventana, el Intersection Observer te notifica cuando se produce una intersección específica.
.
Aquí tienes un ejemplo práctico de cómo utilizar el Intersection Observer:

// Selecciona el elemento que deseas observar
const targetElement = document.querySelector('.mi-elemento');

// Crea una nueva instancia del Intersection Observer
const observer = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // El elemento está dentro del viewport
      console.log('El elemento es visible');
    } else {
      // El elemento ya no está dentro del viewport
      console.log('El elemento ya no es visible');
    }
  });
});

// Observa el elemento seleccionado
observer.observe(targetElement);

En este ejemplo, seleccionamos un elemento con la clase “mi-elemento” y creamos una nueva instancia del Intersection Observer. Luego, definimos una función de devolución de llamada que se ejecutará cada vez que se produzca una intersección. La función de devolución de llamada recibe una matriz de objetos entries, donde cada objeto representa un elemento observado.
.
Dentro de la función de devolución de llamada, verificamos la propiedad isIntersecting de cada objeto entry. Si es true, significa que el elemento está dentro del viewport y lo registramos en la consola. Si es false, significa que el elemento ya no está dentro del viewport.
.
Finalmente, llamamos al método observe del observador para comenzar a observar el elemento seleccionado. Cuando se produzca una intersección, se llamará a la función de devolución de llamada.
.
Esta es solo una introducción básica al Intersection Observer. Puedes ajustar su configuración para realizar tareas más avanzadas, como cargar contenido adicional cuando un elemento se vuelva visible o cambiar estilos en respuesta a la visibilidad.

Mi solución fue utilizar el atributo loading de la etiqueta img

sin JavaScript, solo html

<img src="..." alt="..." loading="lazy">

MI solución:

function createObserver () {
  const callback = (entries, observer) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const lazyImage = entry.target
        lazyImage.src = lazyImage.dataset.img
        observer.unobserve(lazyImage)
        console.log(lazyImage)
      }
    })
  }
  const observer = new IntersectionObserver(callback)
  const target = document.querySelectorAll('img')

  target.forEach((img) => {
    observer.observe(img)
  })
}
function createMovies (movies, container) {
    const imagesURL = 'https://image.tmdb.org/t/p/w300'

  movies.forEach((movie) => {
    
    movieImg.setAttribute('data-img', `${imagesURL}${movie.poster_path}`)
    movieImg.setAttribute('loading', 'lazy')
  })
  createObserver()
}

Rayos!!! que complejo pero que util…

Jmm estaba preocupado cuando vi que muchos se complicaron con funciones, condiciones y demás cosas que a mi se mi hicieron innecesarias

Yo simplemente creé una constante con las opciones, y la función que utilizaría como callback fuera de la función que anteriormente hice para crear las movie cards:

const options = {
    root: null,
    threshold: 0.1,
}
function loadImg(entries) {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img_src = entry.target.getAttribute('img_src')
            entry.target.src = img_src
        }
    })
}

Y ahora en la funcion encargada de crear las movie cards inserto el observer a cada imagen:

function createMovieCard(movie) {
    ...
    const movie_img = document.createElement('img');
    ...

    movie_img.setAttribute('img_src', `https://www.themoviedb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`)
   ...
    movie_img.addEventListener('click', () => {
        location.hash = `#movie=${movie.id}`
    });
    ...
    
    let target = movie_img
    observer.observe(target)

    return scroller_card
}

Si esto podría ser mejorable o tiene alguna desventaja sobre alguna otra forma de hacerlo díganmelo por favor

Logrado, insistí, estuve a punto de rendirme, pero lo logré, con ayuda de los apuntes del curso de manipulación del DOM, el proyecto de Lazy Loading.

Aquí el código:

let callback =(entries) => {
    entries.forEach(element => { 
        if(element.isIntersecting){
            const image = element.target
            const url = image.dataset.src
            image.src = url
        }
    })
}
let observer = new IntersectionObserver(callback)

const registerImage = (imagen) => {
    observer.observe(imagen);
}

y lo llamé en la función que cre las imágenes:

function createMovies(movies, container){
    container.innerHTML="";
    movies.forEach(movie =>  {
        const movieContainer =document.createElement('div')
        movieContainer.classList.add('movie-container');

        // evento de click para acceder a los detalles de la película
        movieContainer.addEventListener('click', () => location.hash = '#movie=' + movie.id);

        const movieImg = document.createElement('img')
        movieImg.classList.add('movie-img')
        movieImg.setAttribute('alt', movie.title);
        
        movieImg.dataset.src = 'https://image.tmdb.org/t/p/w300'+ movie.poster_path
        // movieImg.setAttribute(
        //     'src', 
        //     'https://image.tmdb.org/t/p/w300'+ movie.poster_path,
        //     );
            movieContainer.appendChild(movieImg)
            container.appendChild(movieContainer)
            
        // intersection observer
        registerImage(movieImg)
    });
}

Mi solución:

Intersection Observer example (or should I say masterclass XD)

Mi solucion fue bastante simple, pero funciona bien, en la funcion que uso para cargar todas las imagenes ejecuto una funcion que es la siguiente…

function setObserver (element, url, container) {

  const options = {
    root: container,
  }

  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      console.log(entry)

      entry.isIntersecting && element.setAttribute('src', `https://image.tmdb.org/t/p/w300/${url}`)
    })
  },options)

  observer.observe(element)
}

por si les interesa ver como es llamada tambien les dejo el codigo de la funcion que carga las imagenes

function imagesList(containerHTML, data) {
  containerHTML.innerHTML = ''

  data.forEach((movie, index) => {
    const movieContainer = document.createElement('div')
    movieContainer.classList.add('movie-container')
    movieContainer.classList.add('movie-container--loading')
    movieContainer.addEventListener('click',  () => location.hash = `#movie=${movie.id}`)
    movieContainer.style.animationDelay = `.${index}s`

    const movieImg = document.createElement('img')
    movieImg.classList.add('movie-img')
    movieImg.setAttribute('alt', movie.title)
    setObserver(movieImg, `https://image.tmdb.org/t/p/w300/${movie.poster_path}`, containerHTML)
    movieImg.addEventListener('load', () => movieContainer.classList.remove('movie-container--loading'))
    
    movieContainer.appendChild(movieImg)
    containerHTML.appendChild(movieContainer)
  }); 

aclaro por si acaso, el evento load que escucho es solo para que la imagen tenga la animacion de carga hasta que ya este renderizada para el usuario, esto se hace individualmente para cada imagen y con el delay genera una animacion con toda la lista

.

Despues de leer la documentación y algunos ayuditas de google llegue a sta solución.

let options = {
    rootMargin: '0px',
    threshold: 0.1
}

const callback = (entries) => {

    console.log(entries);
    entries.forEach((entry) => {

        if(entry.isIntersecting){
            let currentElement = entry.target;
            let image = entry.target.dataset.image;
            entry.target.src = image;
            observer.unobserve(currentElement);
        }
    });
}

let observer = new IntersectionObserver(callback, options);

export const addImage = (image) => observer.observe(image);

La función de addImage la agregue a la funcioń principara para cargar todo tipo de imagenes y peliculas y la mande a llamar dentro del map para registrar cada una de las imagenes.
Por lo que vi es buena practica no seguir observando en cada momento el elemento.

import { CustomAxios , d} from '../customAxios/customAxios.js';
import { addImage } from '../lazyLoading/observerImage.js';
export const getAndAppendMovies = async (path, parentContainer, optionalConfig = {}) => {
    try {
        const { data } = await CustomAxios(path, optionalConfig);
        const movies = data.results;
        const $fragment = d.createDocumentFragment();
        
        //console.log(data);
        if(data.total_results === 0) {

            parentContainer.innerHTML = `
                <h2 style="color: #f00; height: 60vh;">
                    Total de resultados: ${data.total_results}
                </h2>
            `;
            return;
        }

        movies.map( movie => {

            console.log("Pelicula creada!");
            const $movieContainer = d.createElement('div');
            $movieContainer.classList.add('movie-container');

            const $img = d.createElement('img');
            $img.classList.add('movie-img');
            $img.dataset.image = `https://image.tmdb.org/t/p/w300/${movie.poster_path}`;
            $img.dataset.id = movie.id;
            $img.alt = `${movie.title}`;
            $movieContainer.appendChild($img);
            $fragment.appendChild($movieContainer);

            //Aquí está!
            addImage($img);
        });

        parentContainer.innerHTML = '';
        parentContainer.appendChild($fragment);
    }catch(err) {

        console.log(err);
    }
    
};```

Primer comentario.