Scroll Infinito con HTMX

  ¿Qué es el Scroll Infinito? 🚀

El scroll infinito es cuando el contenido se carga automáticamente al hacer scroll hacia abajo. Con HTMX y el trigger revealed, esto es súper fácil de implementar.

revealed = se activa cuando un elemento se vuelve visible en pantalla.

Concepto Básico


Imagina una lista de usuarios. En lugar de mostrar todos de una vez, mostramos 5 usuarios y cuando el usuario hace scroll hasta el final, automáticamente cargamos 5 más.

  Cómo funciona

  1. Muestras los primeros usuarios
  2. Al final pones un elemento con hx-trigger="revealed"
  3. Cuando ese elemento se ve, HTMX pide más usuarios
  4. Los nuevos usuarios se agregan a la lista
  5. Se repite el proceso

Ejemplo Súper Básico


1Archivo de Datos (users.json)

Primero creamos un archivo JSON con 50 usuarios simulados:

json
[
{"id": 1, "name": "Ana García", "email": "ana@email.com", "city": "Madrid"},
{"id": 2, "name": "Carlos López", "email": "carlos@email.com", "city": "Barcelona"},
{"id": 3, "name": "María Rodríguez", "email": "maria@email.com", "city": "Valencia"},
{"id": 4, "name": "José Martínez", "email": "jose@email.com", "city": "Sevilla"},
{"id": 5, "name": "Laura Sánchez", "email": "laura@email.com", "city": "Bilbao"},
{"id": 6, "name": "David Pérez", "email": "david@email.com", "city": "Málaga"},
{"id": 7, "name": "Carmen Gómez", "email": "carmen@email.com", "city": "Murcia"},
{"id": 8, "name": "Miguel Torres", "email": "miguel@email.com", "city": "Palma"},
{"id": 9, "name": "Isabel Ruiz", "email": "isabel@email.com", "city": "Córdoba"},
{"id": 10, "name": "Antonio Díaz", "email": "antonio@email.com", "city": "Valladolid"},
{"id": 11, "name": "Pilar Moreno", "email": "pilar@email.com", "city": "Vigo"},
{"id": 12, "name": "Francisco Muñoz", "email": "francisco@email.com", "city": "Gijón"},
{"id": 13, "name": "Rosa Álvarez", "email": "rosa@email.com", "city": "Alicante"},
{"id": 14, "name": "Manuel Romero", "email": "manuel@email.com", "city": "Santander"},
{"id": 15, "name": "Dolores Navarro", "email": "dolores@email.com", "city": "Toledo"},
{"id": 16, "name": "Jesús Herrera", "email": "jesus@email.com", "city": "Badajoz"},
{"id": 17, "name": "Concepción Jiménez", "email": "concepcion@email.com", "city": "Logroño"},
{"id": 18, "name": "Javier Martín", "email": "javier@email.com", "city": "Pamplona"},
{"id": 19, "name": "Teresa Vargas", "email": "teresa@email.com", "city": "Cádiz"},
{"id": 20, "name": "Alejandro Castillo", "email": "alejandro@email.com", "city": "Huelva"},
{"id": 21, "name": "Mercedes Ortega", "email": "mercedes@email.com", "city": "Jaén"},
{"id": 22, "name": "Fernando Delgado", "email": "fernando@email.com", "city": "Ourense"},
{"id": 23, "name": "Josefa Morales", "email": "josefa@email.com", "city": "Ávila"},
{"id": 24, "name": "Rafael Gutiérrez", "email": "rafael@email.com", "city": "Salamanca"},
{"id": 25, "name": "Antonia Serrano", "email": "antonia@email.com", "city": "Teruel"},
{"id": 26, "name": "Ramón Castro", "email": "ramon@email.com", "city": "Zamora"},
{"id": 27, "name": "Francisca Ortiz", "email": "francisca@email.com", "city": "Cuenca"},
{"id": 28, "name": "Eduardo Rubio", "email": "eduardo@email.com", "city": "Soria"},
{"id": 29, "name": "Amparo Moya", "email": "amparo@email.com", "city": "Segovia"},
{"id": 30, "name": "Sergio Pardo", "email": "sergio@email.com", "city": "Palencia"},
{"id": 31, "name": "Encarnación Cano", "email": "encarnacion@email.com", "city": "León"},
{"id": 32, "name": "Andrés Prieto", "email": "andres@email.com", "city": "Burgos"},
{"id": 33, "name": "Remedios Peña", "email": "remedios@email.com", "city": "Albacete"},
{"id": 34, "name": "Emilio Vega", "email": "emilio@email.com", "city": "Cáceres"},
{"id": 35, "name": "Milagros Blanco", "email": "milagros@email.com", "city": "Lugo"},
{"id": 36, "name": "Tomás Medina", "email": "tomas@email.com", "city": "Huesca"},
{"id": 37, "name": "Esperanza Calvo", "email": "esperanza@email.com", "city": "Guadalajara"},
{"id": 38, "name": "Ignacio Campos", "email": "ignacio@email.com", "city": "Ceuta"},
{"id": 39, "name": "Purificación Garrido", "email": "purificacion@email.com", "city": "Melilla"},
{"id": 40, "name": "Rubén Santana", "email": "ruben@email.com", "city": "Girona"},
{"id": 41, "name": "Inmaculada Domínguez", "email": "inmaculada@email.com", "city": "Lleida"},
{"id": 42, "name": "Ángel Vázquez", "email": "angel@email.com", "city": "Tarragona"},
{"id": 43, "name": "Nieves Reyes", "email": "nieves@email.com", "city": "Castellón"},
{"id": 44, "name": "Pablo Iglesias", "email": "pablo@email.com", "city": "Pontevedra"},
{"id": 45, "name": "Virtudes Santos", "email": "virtudes@email.com", "city": "Orense"},
{"id": 46, "name": "Gonzalo Lozano", "email": "gonzalo@email.com", "city": "Almería"},
{"id": 47, "name": "Asunción Guerrero", "email": "asuncion@email.com", "city": "Granada"},
{"id": 48, "name": "Cristóbal Mendoza", "email": "cristobal@email.com", "city": "Jaén"},
{"id": 49, "name": "Soledad Cortés", "email": "soledad@email.com", "city": "Cádiz"},
{"id": 50, "name": "Esteban Herrero", "email": "esteban@email.com", "city": "Huelva"}
]

2Backend PHP Simple

Usaremos PHP para cargar usuarios desde el archivo JSON y devolverlos en formato HTML, limitando la cantidad mostrada por página y mostrando un mensaje cuando no hay más usuarios disponibles.

php
<?php
  // Archivo: users.php
  header('Content-Type: text/html; charset=utf-8');

  // Obtener página (por defecto 1)
  $page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
  $limit = 5; // 5 usuarios por página

  // Leer el archivo JSON
  $json = file_get_contents('users.json');
  $allUsers = json_decode($json, true);

  // Calcular qué usuarios mostrar
  $offset = ($page - 1) * $limit;
  $users = array_slice($allUsers, $offset, $limit);
  $hasMore = count($allUsers) > ($offset + $limit);

  // Mostrar los usuarios
  foreach ($users as $user) {
  echo '<div class="user-card">';
  echo '<h3>' . htmlspecialchars($user['name']) . '</h3>';
  echo '<p>📧 ' . htmlspecialchars($user['email']) . '</p>';
  echo '<p>🏙️ ' . htmlspecialchars($user['city']) . '</p>';
  echo '</div>';
  }

  // Si hay más usuarios, agregar el trigger
  if ($hasMore) {
  $nextPage = $page + 1;
  echo '<div hx-get="users.php?page=' . $nextPage . '" ';
  echo 'hx-trigger="revealed" ';
  echo 'hx-swap="outerHTML">';
  echo '<p>⏳ Cargando más usuarios...</p>';
  echo '</div>';
  } else {
  // Mensaje cuando no hay más usuarios
  echo '<div class="end-message">';
  echo '<p>✅ ¡Has visto todos los usuarios disponibles!</p>';
  echo '</div>';
  }
?>

3HTML Principal

Usaremos HTML para crear la estructura básica de la página, incluyendo el contenedor principal y el trigger para la carga automática.

html
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Scroll Infinito - Ejemplo Básico</title>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js"></script>
<style>
    body {
        font-family: Arial, sans-serif;
        max-width: 600px;
        margin: 0 auto;
        padding: 20px;
    }
    .user-card {
        border: 1px solid #ddd;
        padding: 15px;
        margin: 10px 0;
        border-radius: 8px;
        background: #f9f9f9;
    }
    .user-card h3 {
        margin: 0 0 10px 0;
        color: #333;
    }
    .user-card p {
        margin: 5px 0;
        color: #666;
    }
    .end-message {
        text-align: center;
        padding: 20px;
        margin: 20px 0;
        background: #e8f5e8;
        border: 1px solid #4caf50;
        border-radius: 8px;
        color: #2e7d32;
    }
    .end-message p {
        margin: 0;
        font-weight: bold;
    }
</style>
</head>
<body>
<h1>👥 Lista de Usuarios</h1>
<p>Scroll hacia abajo para cargar más usuarios automáticamente</p>

<div id="users-container" 
     hx-get="users.php?page=1" 
     hx-trigger="load">
    <p>⏳ Cargando usuarios...</p>
</div>
</body>
</html>

4Explicación Detallada

  Paso 1: Carga inicial

Cuando la página se carga, hx-trigger="load" ejecuta users.php?page=1 y muestra los primeros 5 usuarios.

  Paso 2: Elemento trigger

Al final de cada grupo de usuarios, PHP agrega un div con hx-trigger="revealed" que apunta a la siguiente página.

  Paso 3: Scroll automático

Cuando haces scroll y ese div se vuelve visible, HTMX automáticamente pide users.php?page=2.

  Paso 4: Agregar contenido

Los nuevos usuarios se agregan después del elemento actual con hx-swap="afterend".

Recuerda que puedes descargar el código completo de este ejemplo en el repositorio de GitHub: scroll-infinito-con-htmx-y-php.

Atributos Clave de HTMX


AtributoQué hace
hx-get="users.php?page=2"Hace petición GET a esa URL
hx-trigger="revealed"Se activa cuando el elemento es visible
hx-swap="afterend"Inserta el contenido después del elemento
hx-trigger="load"Se activa cuando la página carga

Ejemplo Más Simple


Si quieres algo aún más simple sin archivos JSON externos, aquí tienes la versión más básica posible. Solo necesitas 2 archivos, simple.php y index.html.

Archivo PHP (simple.php): Este archivo simula usuarios y devuelve una cantidad limitada de usuarios por página.

php
<?php
// simple.php
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;

// Simular usuarios directamente en PHP
$users = [
    "Ana García", "Carlos López", "María Rodríguez", "José Martínez", "Laura Sánchez",
    "David Pérez", "Carmen Gómez", "Miguel Torres", "Isabel Ruiz", "Antonio Díaz",
    "Pilar Moreno", "Francisco Muñoz", "Rosa Álvarez", "Manuel Romero", "Dolores Navarro",
    "Ana García", "Carlos López", "María Rodríguez", "José Martínez", "Laura Sánchez", 
    "Isabel Ruiz", "Antonio Díaz", "Pilar Moreno", "Francisco Muñoz", "David Pérez",
    "Rosa Álvarez", "Manuel Romero", "Dolores Navarro", "Jesús Herrera", "Concepción Jiménez", 
    "Javier Martín", "Teresa Vargas", "Alejandro Castillo","Carmen Hostetler", "Miguel Fernández",
];

$start = ($page - 1) * 3; // 3 por página
$end = $start + 3;
$hasMore = $end < count($users);

for ($i = $start; $i < $end && $i < count($users); $i++) {
    echo '<div style="padding:10px; border:1px solid #ccc; margin:5px;">';
    echo '<h4>' . $users[$i] . '</h4>';
    echo '<p>Usuario #' . ($i + 1) . '</p>';
    echo '</div>';
}

if ($hasMore) {
    echo '<div hx-get="simple.php?page=' . ($page + 1) . '" hx-trigger="revealed" hx-swap="outerHTML">';
    echo '<p style="text-align: center; color: #666;">⏳ Cargando...</p>';
    echo '</div>';
} else {
    echo '<div style="text-align: center; padding: 20px; background: #e8f5e8; border: 1px solid #4caf50; border-radius: 8px; margin: 10px 0; color: #2e7d32;">';
    echo '<p style="margin: 0; font-weight: bold;">✅ ¡Has visto todos los usuarios disponibles!</p>';
    echo '</div>';
}
?>

Archivo HTML (index.html): Aquí tienes el archivo index.html que carga los usuarios usando simple.php.

html
<!DOCTYPE html>
  <html lang="es">
  <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Scroll Infinito - Ejemplo Minimalista</title>
      <script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js"></script>
      <style>
          body {
              font-family: Arial, sans-serif;
              max-width: 500px;
              margin: 20px auto;
              padding: 20px;
              background: #f5f5f5;
          }
          h1 {
              text-align: center;
              color: #333;
          }
          .container {
              background: white;
              padding: 20px;
              border-radius: 10px;
              box-shadow: 0 2px 10px rgba(0,0,0,0.1);
          }
      </style>
  </head>
  <body>
      <h1>🚀 Scroll Infinito Minimalista</h1>
      <div class="container">
          <p><strong>Instrucciones:</strong> Scroll hacia abajo para ver más usuarios cargarse automáticamente.</p>
          
          <div id="users-list" 
              hx-get="simple.php?page=1" 
              hx-trigger="load">
              <p style="text-align: center;">⏳ Cargando usuarios...</p>
          </div>
      </div>
  </body>
  </html>

Ventajas de Este Enfoque


  ¿Por qué usar HTMX para scroll infinito?

  • Súper simple: Solo 3 atributos HTML
  • Sin JavaScript: HTMX maneja todo
  • Funciona siempre: Compatible con cualquier servidor
  • Fácil de entender: El código se explica solo
  • Rápido de implementar: En minutos tienes scroll infinito

¡Y eso es todo! Con estos ejemplos básicos ya entiendes cómo funciona el scroll infinito con HTMX. Es realmente así de simple.