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.
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.
hx-trigger="revealed"Primero creamos un archivo JSON con 50 usuarios simulados:
[
{"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"}
] 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
// 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>';
}
?> Usaremos HTML para crear la estructura básica de la página, incluyendo el contenedor principal y el trigger para la carga automática.
<!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> Cuando la página se carga, hx-trigger="load" ejecuta users.php?page=1 y muestra los primeros 5 usuarios.
Al final de cada grupo de usuarios, PHP agrega un div con hx-trigger="revealed" que apunta a la siguiente página.
Cuando haces scroll y ese div se vuelve visible, HTMX automáticamente pide users.php?page=2.
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.
| Atributo | Qué 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 |
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
// 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.
<!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> ¡Y eso es todo! Con estos ejemplos básicos ya entiendes cómo funciona el scroll infinito con HTMX. Es realmente así de simple.