Infinite Scroll sin botón en JS vanilla

Resumen

¿Te imaginas reemplazar ese botón de "cargar más" por una experiencia fluida donde las películas aparezcan solas al hacer scroll? Eso es justo lo que vas a lograr con infinite scroll en JavaScript vanilla, usando tres propiedades del DOM y un poco de lógica dinámica para que funcione en cualquier ruta de tu aplicación.

Esta técnica es ideal si estás construyendo una app tipo Netflix, una galería de productos o cualquier vista paginada donde quieras mejorar la experiencia del usuario sin depender de frameworks.

Qué propiedades del DOM detectan el final del scroll

Para saber cuándo el usuario llegó al final de la página necesitas combinar tres propiedades de document.documentElement. Cada una mide algo distinto y juntas te dan la fórmula mágica.

  • scrollTop: cuánto scroll ha hecho el usuario desde la parte superior. Si está arriba del todo, vale 0; al bajar, el número aumenta [01:30].
  • clientHeight: el alto visible de la ventana del navegador. Cambia si haces resize o si el usuario está en un iPad versus en un móvil [02:25].
  • scrollHeight: el total de scroll posible en esa página. Es un valor fijo que no cambia con el scroll del usuario [03:05].

¿Cómo sé si el usuario llegó al final del scroll? Suma scrollTop + clientHeight y compáralo con scrollHeight. Si la suma es mayor o igual, el usuario tocó fondo.

Por qué restar 15 píxeles al scrollHeight

En la práctica, esa comparación exacta casi nunca devuelve true por culpa de decimales y diferencias de renderizado entre navegadores. El truco que ahorra horas de debugging en Stack Overflow es restar un margen de entre 5 y 15 píxeles al scrollHeight. Así la condición se activa justo antes del final real y todo fluye.

js const { scrollTop, scrollHeight, clientHeight } = document.documentElement; const scrollIsBottom = (scrollTop + clientHeight) >= (scrollHeight - 15);

Esa constante scrollIsBottom es mucho más legible dentro de un if que arrastrar toda la fórmula con paréntesis.

Cómo escuchar el evento scroll con addEventListener

Una vez que tienes la validación, necesitas conectarla a un evento. Aquí entra window.addEventListener('scroll', ...), que dispara la función cada vez que el usuario mueve el scroll [07:10].

js window.addEventListener('scroll', getPaginatedTrendingMovies, { passive: false });

Esto funciona perfecto para una sola ruta, pero tiene un problema: si tu app tiene varias páginas (tendencias, categorías, búsquedas, detalle de película), no quieres llamar siempre a la misma función. Necesitas algo dinámico.

Cómo hacer que infinite scroll funcione en cualquier ruta

La solución es crear una variable global llamada infiniteScroll que actúe como puntero a la función paginada de la ruta activa. Cada vez que el usuario navega a una nueva sección, esa variable apunta a la función correcta.

En el archivo de navegación declaras:

js let page = 1; let infiniteScroll;

Y dentro de cada función de ruta, le asignas la función paginada que corresponda:

js // dentro de trendsPage infiniteScroll = getPaginatedTrendingMovies;

Fíjate que no la ejecutas con paréntesis: solo guardas la referencia. Los paréntesis los pone después el addEventListener cuando dispara el scroll.

Por qué hay que limpiar el listener al cambiar de ruta

Aquí viene la parte enredada que confunde a muchos. Si el usuario navega de tendencias a una categoría, el viejo listener sigue vivo y puede ejecutar la función equivocada. Además, el addEventListener se enganchó cuando infiniteScroll aún era undefined, así que no "se entera" de cambios posteriores.

La solución es remover el listener al inicio de la función navigator y volver a registrarlo después con el nuevo valor [12:40].

js if (infiniteScroll) { window.removeEventListener('scroll', infiniteScroll, { passive: false }); infiniteScroll = undefined; }

// luego se ejecuta la función de la ruta actual // y al final:

if (infiniteScroll) { window.addEventListener('scroll', infiniteScroll, { passive: false }); }

Con este patrón limpias, reasignas y vuelves a escuchar. El evento de scroll siempre apunta a la función correcta de la ruta donde está el usuario.

¿Qué hace passive: false en addEventListener? Es el tercer argumento que controla si el listener puede o no llamar a preventDefault() sobre el evento de scroll, lo que afecta el rendimiento del navegador.

El reto: descifra el passive false

Ese passive: false que aparece como tercer argumento del addEventListener tiene una razón específica relacionada con cómo el navegador optimiza el scroll. Tu tarea es investigar qué hace esa propiedad y dejar tu explicación en los comentarios. Pista: tiene que ver con el rendimiento y con la posibilidad de cancelar el evento.

Qué sigue después de tener infinite scroll funcionando

Con esta arquitectura ya puedes aplicar infinite scroll a cualquier vista de tu app:

  1. Crea la función que consume la API de forma paginada.
  2. Asígnala a la variable infiniteScroll dentro de la función de esa ruta.
  3. Deja que el navigator se encargue de registrar y limpiar el listener.

En la siguiente entrega vas a aprender a limitar la cantidad de páginas que se cargan, porque ninguna API te entrega resultados infinitos y no tiene sentido seguir disparando peticiones cuando ya no hay más contenido.

¿Lograste implementarlo en otra ruta de tu app? Cuéntame en los comentarios qué función paginada le asignaste a tu infiniteScroll.