Back

Construyendo Scroll Infinito con HTMX

Construyendo Scroll Infinito con HTMX

Cargar contenido a medida que los usuarios se desplazan elimina los clics de paginación y crea experiencias de navegación más fluidas. Pero implementar scroll infinito tradicionalmente requiere gestionar intersection observers, rastrear el estado y escribir considerable JavaScript. HTMX ofrece un enfoque más simple: scroll infinito impulsado por el servidor usando atributos HTML.

Esta guía cubre el patrón canónico de scroll infinito con HTMX, explica cuándo usar revealed versus intersect, y muestra cómo construir una implementación con mejora progresiva que funciona sin JavaScript.

Puntos Clave

  • El scroll infinito de HTMX utiliza un patrón de elemento cargador auto-perpetuante donde cada respuesta del servidor incluye el siguiente cargador
  • Usa revealed para desplazamiento de página completa e intersect once para contenedores desplazables con overflow
  • Construye sobre enlaces de paginación estándar para mejora progresiva que funciona sin JavaScript
  • El servidor controla todo el estado de paginación a través del elemento cargador en cada respuesta

Cómo Funciona el Scroll Infinito Impulsado por el Servidor

El patrón central coloca atributos HTMX en un elemento “cargador”—típicamente el último elemento de tu lista. Cuando este elemento se vuelve visible, HTMX solicita la siguiente página de tu servidor. El servidor devuelve HTML que contiene tanto los nuevos elementos como un nuevo elemento cargador que apunta a la página subsiguiente.

Aquí está la estructura básica:

<div id="items">
  <div class="item">First item</div>
  <div class="item">Second item</div>

  <!-- Last rendered item doubles as the loader -->
  <div class="item"
       hx-get="/items?page=2"
       hx-trigger="revealed"
       hx-swap="afterend">
    Last item of page 1
  </div>
</div>

La respuesta del servidor para la página 2 incluye elementos y un nuevo cargador:

<div class="item">First item of page 2</div>
<div class="item">Second item of page 2</div>

<div class="item"
     hx-get="/items?page=3"
     hx-trigger="revealed"
     hx-swap="afterend">
  Last item of page 2
</div>

Este patrón auto-perpetuante continúa hasta que el servidor devuelve contenido sin un cargador, señalando el final de los datos disponibles.

HTMX Intersect vs Revealed: Eligiendo el Disparador Correcto

HTMX proporciona dos disparadores basados en visibilidad, y seleccionar el incorrecto causa errores comunes.

Usa revealed para desplazamiento de página completa donde el documento mismo se desplaza. Este disparador se activa cuando un elemento entra en el viewport del navegador.

Usa intersect once cuando el contenido vive dentro de un contenedor desplazable con CSS como overflow-y: scroll. El disparador revealed observa el viewport del documento, no elementos desplazables individuales, por lo que no se activará correctamente en contenedores con overflow. El disparador intersect se basa en la moderna Intersection Observer API, que está soportada en todos los navegadores principales.

<!-- Full-page scroll: use revealed -->
<div hx-trigger="revealed" hx-get="/more">...</div>

<!-- Scrollable container: use intersect once -->
<div class="scroll-container" style="overflow-y: scroll; height: 400px;">
  <div hx-trigger="intersect once" hx-get="/more">...</div>
</div>

El modificador once previene solicitudes duplicadas si el elemento entra y sale repetidamente del umbral de intersección.

Estrategias de Intercambio: afterend vs beforeend

Tu estrategia de intercambio determina dónde aparece el nuevo contenido y cómo prevenir cargas duplicadas.

afterend inserta contenido inmediatamente después del elemento cargador. Usa esto cuando el cargador es el último elemento renderizado—la respuesta aparece debajo de él.

beforeend añade contenido dentro de un contenedor objetivo. Combina esto con hx-target para especificar dónde van los elementos:

<div hx-get="/items?page=2"
     hx-trigger="revealed"
     hx-target="#items"
     hx-swap="beforeend">
  Loading indicator...
</div>

Con beforeend, tu cargador típicamente se sitúa fuera del contenedor de elementos y se reemplaza o elimina después de que se carga la página final.

Mejora Progresiva con Paginación Estándar

El scroll infinito con HTMX debe construirse sobre la paginación existente, no reemplazarla completamente. Comienza con enlaces de paginación funcionales:

<div id="items">
  {% for item in items %}
    <div class="item">{{ item.name }}</div>
  {% endfor %}
</div>

<a href="/items?page={{ next_page }}">Load more</a>

Luego actualiza el enlace de paginación con atributos HTMX:

<a href="/items?page={{ next_page }}"
   hx-get="/items?page={{ next_page }}"
   hx-trigger="revealed"
   hx-target="#items"
   hx-swap="beforeend"
   hx-select="#items > *">
  Load more
</a>

Los usuarios sin JavaScript ven paginación estándar. Los usuarios con HTMX obtienen scroll infinito. El endpoint del servidor permanece idéntico para ambos.

Actualizando Elementos de UI con Intercambios Fuera de Banda

A veces necesitas actualizar elementos fuera del área de contenido principal—como contadores de elementos o estados de carga. Los intercambios fuera de banda de HTMX manejan esto:

<!-- In server response -->
<div class="item">New item</div>
<span id="item-count" hx-swap-oob="outerHTML">Showing 20 of 100</span>

El atributo hx-swap-oob="outerHTML" le dice a HTMX que encuentre y reemplace el elemento con ID coincidente en cualquier lugar de la página, independientemente del objetivo de intercambio principal.

Estrategias de Paginación del Lado del Servidor

Tu servidor controla el estado de paginación completamente. Dos enfoques comunes:

Paginación por offset usa números de página o valores de desplazamiento: /items?page=3 o /items?offset=20. Simple de implementar pero puede mostrar duplicados si se añaden elementos durante la navegación.

Paginación por cursor usa un puntero al último elemento visto: /items?after=item_xyz. Más confiable para contenido dinámico pero requiere identificadores estables.

Cualquier enfoque funciona con HTMX—el cliente simplemente pasa cualquier parámetro que el servidor proporcione en el siguiente elemento cargador.

Conclusión

El scroll infinito de HTMX mueve la complejidad al servidor donde la lógica de paginación ya existe. Elige revealed para desplazamiento de documento e intersect once para contenedores con overflow. Construye sobre paginación estándar para que tu funcionalidad degrade elegantemente. Deja que el servidor impulse el estado a través del elemento cargador de cada respuesta en lugar de rastrear páginas del lado del cliente.

Preguntas Frecuentes

Esto usualmente sucede cuando usas intersect sin el modificador once. El elemento puede cruzar repetidamente el umbral de intersección a medida que el contenido se carga y cambia el diseño. Añade once a tu disparador como hx-trigger intersect once para asegurar que solo se dispare una única solicitud por elemento cargador.

Añade un atributo hx-indicator apuntando a tu elemento de carga. Por ejemplo hx-indicator con un valor de hash loading en tu elemento cargador y un span separado con id loading conteniendo tu spinner. HTMX automáticamente muestra este elemento durante las solicitudes y lo oculta cuando se completan.

HTMX ayuda a prevenir solicitudes superpuestas, y cada elemento cargador típicamente está configurado para dispararse una vez. La respuesta del servidor determina la siguiente página. Si necesitas coordinación más estricta puedes usar hx-sync para controlar el comportamiento de las solicitudes.

Sí. Cuando los filtros cambian, reinicia tu contenedor de contenido y actualiza la URL del cargador para incluir parámetros de filtro. El servidor maneja la lógica de filtrado y devuelve la primera página apropiada. Cada cargador subsiguiente incluye los mismos parámetros de filtro para mantener consistencia entre páginas.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay