Observador de intersecciones | Creando scroll infinito en tu aplicación web

Nov 25 2022
Si no eres una persona de las cavernas o no odias a Jeff Bezos (no importa si lo haces) debes haber comprado en línea. Cuando busca un artículo en estas tiendas en línea, se le presenta una lista de resultados.
Foto de SpaceX en Unsplash

Si no eres una persona de las cavernas o no odias a Jeff Bezos (no importa si lo haces) debes haber comprado en línea. Cuando busca un artículo en estas tiendas en línea, se le presenta una lista de resultados. Si no es raro como yo y no está buscando " diente de tigre empapado en sangre humana" , que es demasiado específico, existe la posibilidad de que su consulta de búsqueda obtenga miles de productos. ¿Crees que tu navegador carga todos estos más de 1000 elementos en total? No , eso sería demasiados datos para que el navegador los maneje y calcule la interfaz de usuario. Aún así, sigues obteniendo elementos a medida que llegas al final de tu resultado. Esto se llama pergamino infinito .

Aquí hay un ejemplo de desplazamiento infinito en la página de inicio de YouTube.

Gif explicando pergamino infinito

Hay varias técnicas para lograr esto usando JavaScript, pero la más eficiente es usar una API web dulce llamada IntersectionObserver. Veamos cómo usar esta API para implementar nuestra propia minilista desplazable infinita.

El concepto es observar un elemento html cada vez que aparece en la ventana gráfica y luego activar una devolución de llamada. A continuación se muestra cómo crear este observador.

Vamos a crear un archivo html que se vea así.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Infinte Scroll</title>
</head>
<style>
    .product {
        padding: 20px;
        border: 1px solid black;
        margin-bottom: 20px;
        opacity: 0;
        margin-left: 100px;
        transition: all 300ms;
    }
    .visible {
        opacity: 1;
        margin-left: 0px;
    }
</style>
<body>
    <div id="container">
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
        <div class="product">Product</div>
    </div>

    <script src="./index.js"></script>
</body>
</html>

Primero, obtengamos todas las tarjetas de productos por su nombre de clase.

const cards = document.querySelectorAll('.product');

const productCards= document.querySelectorAll('.product');

const observer = new IntersectionObserver((entries) => {
    // Todo
}, {
    threshold: 0.95,
});

productCards.forEach((card) => {
  observer.observe(card);
})

Otro parámetro en el constructor es un objeto. Este objeto significa las opciones para este observador en particular. En este caso, solo tenemos umbral. umbral aquí es un número que puede tomar cualquier valor de 0 a 1. Esto significa el porcentaje del elemento que estamos observando que debería estar visible en la ventana gráfica para que se considere que se cruza. le hemos dado un valor de 0,95, lo que significa que cuando el 95% de cualquier tarjeta de producto entre en la ventana gráfica, se considerará que esa tarjeta se cruza con la ventana gráfica.

Volvamos a nuestro archivo index.html . También tenemos otra clase de estilo visible definida en la etiqueta con estilo. Este estilo, cuando se aplica a nuestra tarjeta de producto, hará que la tarjeta sea visible nuevamente con una pequeña animación ( propiedad de transición ).

Hagamos cambios en nuestro observador de modo que todas las tarjetas de productos que estén al menos en un 95 % en la ventana gráfica sean visibles y el resto se oculte.

const productCards = document.querySelectorAll('.product');

const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        entry.target.classList.toggle('visible', entry.isIntersecting);
    })
}, {
    threshold: 0.95
});

productCards.forEach((card) => {
    observer.observe(card);
});

Sin embargo, hay una trampa. Cuando nos desplazamos hacia arriba, debido a que tenemos una lógica para alternar la clase visible cuando el elemento se cruza, también elimina esta clase cuando no lo es. Entonces, cuando nos desplazamos hacia arriba, vemos que nuestras tarjetas desaparecen de la parte superior. Para solucionar esto, dejaremos de observar las cartas que ya se han cruzado.

const productCards = document.querySelectorAll('.product');

const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        entry.target.classList.toggle('visible', entry.isIntersecting);
        // unobserving entries which have already intersected
        if (entry.isIntersecting) observer.unobserve(entry.target);
    })
}, {
    threshold: 0.95
});

productCards.forEach((card) => {
    observer.observe(card);
});

Primero, crearemos otro observador que solo observará la última carta.

const lastCardObserver = new IntersectionObserver((entries) => {
    const lastCard = entries[0];
    if (!lastCard.isIntersecting) return;
    loadMoreCards();
    lastCardObserver.unobserve(lastCard.target);
    lastCardObserver.observe(document.querySelector('.product:last-child'));
}, {
    threshold: 0.95
});

lastCardObserver.observe(document.querySelector('.product:last-child'));

function loadMoreCards() {
    const container = document.getElementById('container');
    for (let i = 0; i < 10; i++) {
        const element = document.createElement('div');
        element.classList.add('product');
        element.innerText = 'Product';
        observer.observe(element);
        container.appendChild(element);
    }
}

Asi es como luce ahora !

Pergamino infinito perfecto!

Ahora, en las aplicaciones del mundo real, en lugar de cargar más tarjetas, buscaremos nuevos datos mediante la API.

Déjame saber cómo puedes usar esta API de forma creativa en tus aplicaciones.