A continuación te ofrezco un tutorial para aprender a hacer un paginador infinito que sea moderno, eficiente y flexible ante cualquier navegador o dispositivo.
Te encuentras en una continuación del artículo crear un paginador con JavaScript. Te recomiendo encarecidamente que lo leas antes de continuar, ya que daré por sentado ciertos conocimientos.
Demostración interactiva
Observa como el scroll del cuadrado amarillo va creciendo según realizas scroll.
HTML
¿Cómo detectamos que hemos llegado al final de la página? La forma más moderna y fiable es usando la API de Observables (dispones de un artículo con un ejemplo sencillo al respecto: Javascript ejecutar cuando una etiqueta sea visible). En este caso usaremos un elemento transparente que cuando sea visible por el usuario, cambiaremos a la página siguiente.
<!-- Un elemento que vamos a vigilar. Si es visible, aumentaremos la "paginaActual" -->
<div id="marcador" style="height: 1px"></div>
const marcadorDOM = document.querySelector("#marcador");
// Creamos un objeto IntersectionObserver
const observerCuadrado = new IntersectionObserver((entries) => {
// Comprobamos todas las intesecciones. En el ejemplo solo existe una: cuadrado
entries.forEach((entry) => {
// Si es observable, entra
if (entry.isIntersecting) {
// Aumentamos la pagina actual
avanzarPagina();
}
});
});
// Añado a mi Observable que quiero observar. En este caso el cuadrado
observerCuadrado.observe(marcadorDOM);
El resto del documento sería igual al anterior artículo pero sin los controles (botones).
<h1>Paginador</h1>
<section>
<!-- Lugar donde generaremos todos los articulos por página -->
<div id="listado-articulos"></div>
<!-- Un elemento que vamos a vigilar. Si es visible, aumentaremos la "paginaActual" -->
<div id="marcador" style="height: 1px"></div>
</section>
<!-- Plantilla del artículo -->
<template id="plantilla-articulo">
<article>
<h2 id="titulo"></h2>
<p id="cuerpo"></p>
</article>
</template>
JavaScript
El código es prácticamente igual al anterior paginador, salvo por un detalle: no limpiamos los artículos anteriores. Constantemente acumulamos los artículos.
// --
// Variables
// --
const listadoArticulosDOM = document.querySelector("#listado-articulos");
const plantillaArticulo = document.querySelector("#plantilla-articulo").content.firstElementChild;
const marcadorDOM = document.querySelector("#marcador");
const elementosPorPagina = 3;
let paginaActual = 1;
const baseDeDatos = []; // Todos los datos del artículo anterior
// --
// Funciones
// --
/**
* Función que cambia de página cuando encuentra el marcador
* @preturn void
*/
function vigilanteDeMarcador() {
// Creamos un objeto IntersectionObserver
const observerMarcador = new IntersectionObserver((entries) => {
// Comprobamos todas las intesecciones. En el ejemplo solo existe una: marcador
entries.forEach((entry) => {
// Si es observable, entra
if (entry.isIntersecting) {
// Aumentamos la pagina actual
avanzarPagina();
}
});
});
// Añado a mi Observable que quiero observar, en este caso el marcador
observerMarcador.observe(marcadorDOM);
}
/**
* Función que pasa a la siguiente página
* @preturn void
*/
function avanzarPagina() {
// Incrementar "paginaActual"
paginaActual = paginaActual + 1;
// Redibujar
renderizar();
}
/**
* Función que retrocedea la página anterior
* @return void
*/
function retrocederPagina() {
// Disminuye "paginaActual"
paginaActual = paginaActual - 1;
// Redibujar
renderizar();
}
/**
* Función que devuelve los datos de la página deseada
* @param {Int) pagina - Número de página
* @return {Array<JSON>}
*/
function obtenerRebanadaDeBaseDeDatos(pagina = 1) {
const corteDeInicio = (paginaActual - 1) * elementosPorPagina;
const corteDeFinal = corteDeInicio + elementosPorPagina;
return baseDeDatos.slice(corteDeInicio, corteDeFinal);
}
/**
* Función que devuelve el número total de páginas disponibles
* @return {Int}
*/
function obtenerPaginasTotales() {
return Math.ceil(baseDeDatos.length / elementosPorPagina);
}
/**
* Función que se encarga de dibujar el nuevo DOM a partir de las variables
* @return void
*/
function renderizar() {
// Obtenemos datos
const rebanadaDatos = obtenerRebanadaDeBaseDeDatos(paginaActual);
//// Dibujamos
// Crear articulos
rebanadaDatos.forEach(function (articulo) {
// Clonar la plantilla
const miArticulo = plantillaArticulo.cloneNode(true);
// Rellenamos los datos del nuevo <article>
const miTitulo = miArticulo.querySelector("#titulo");
miTitulo.textContent = articulo.title;
const miCuerpo = miArticulo.querySelector("#cuerpo");
miCuerpo.textContent = articulo.body;
// Lo insertamos dentro de "listadoArticulosDOM"
listadoArticulosDOM.appendChild(miArticulo);
});
}
// --
// Inicio
// --
renderizar(); // Mostramos la primera página nada más que carge la página
vigilanteDeMarcador();
Resultado final
El siguiente fragmento de código aglutina todo lo mencionado con anterioridad. Esta listo para copiar y pegar.
¡Recuerda que necesitas scroll para que funcione!
<!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8"/>
<title>Paginador infinito</title>
<style>
</style>
</head>
<body>
<h1>Paginador infinito</h1>
<section>
<!-- Lugar donde generaremos todos los articulos por página -->
<div id="listado-articulos"></div>
<!-- Un elemento que vamos a vigilar. Si es visible, aumentaremos la "paginaActual" -->
<div id="marcador" style="height: 1px"></div>
</section>
<!-- Plantilla del artículo -->
<template id="plantilla-articulo">
<article>
<h2 id="titulo"></h2>
<p id="cuerpo"></p>
</article>
</template>
<script>
// --
// Variables
// --
const listadoArticulosDOM = document.querySelector("#listado-articulos");
const plantillaArticulo = document.querySelector("#plantilla-articulo").content.firstElementChild;
const marcadorDOM = document.querySelector("#marcador");
const elementosPorPagina = 3;
let paginaActual = 1;
const baseDeDatos = [
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
},
{
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
},
{
"userId": 1,
"id": 3,
"title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
"body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
},
{
"userId": 1,
"id": 4,
"title": "eum et est occaecati",
"body": "ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit"
},
{
"userId": 1,
"id": 5,
"title": "nesciunt quas odio",
"body": "repudiandae veniam quaerat sunt sed\nalias aut fugiat sit autem sed est\nvoluptatem omnis possimus esse voluptatibus quis\nest aut tenetur dolor neque"
},
{
"userId": 1,
"id": 6,
"title": "dolorem eum magni eos aperiam quia",
"body": "ut aspernatur corporis harum nihil quis provident sequi\nmollitia nobis aliquid molestiae\nperspiciatis et ea nemo ab reprehenderit accusantium quas\nvoluptate dolores velit et doloremque molestiae"
},
{
"userId": 1,
"id": 7,
"title": "magnam facilis autem",
"body": "dolore placeat quibusdam ea quo vitae\nmagni quis enim qui quis quo nemo aut saepe\nquidem repellat excepturi ut quia\nsunt ut sequi eos ea sed quas"
},
{
"userId": 1,
"id": 8,
"title": "dolorem dolore est ipsam",
"body": "dignissimos aperiam dolorem qui eum\nfacilis quibusdam animi sint suscipit qui sint possimus cum\nquaerat magni maiores excepturi\nipsam ut commodi dolor voluptatum modi aut vitae"
},
{
"userId": 1,
"id": 9,
"title": "nesciunt iure omnis dolorem tempora et accusantium",
"body": "consectetur animi nesciunt iure dolore\nenim quia ad\nveniam autem ut quam aut nobis\net est aut quod aut provident voluptas autem voluptas"
},
{
"userId": 1,
"id": 10,
"title": "optio molestias id quia eum",
"body": "quo et expedita modi cum officia vel magni\ndoloribus qui repudiandae\nvero nisi sit\nquos veniam quod sed accusamus veritatis error"
}
];
// --
// Funciones
// --
/**
* Función que cambia de página cuando encuentra el marcador
* @preturn void
*/
function vigilanteDeMarcador() {
// Creamos un objeto IntersectionObserver
const observerMarcador = new IntersectionObserver((entries) => {
// Comprobamos todas las intesecciones. En el ejemplo solo existe una: cuadrado
entries.forEach((entry) => {
// Si es observable, entra
if (entry.isIntersecting) {
// Aumentamos la pagina actual
avanzarPagina();
}
});
});
// Añado a mi Observable que quiero observar. En este caso el cuadrado
observerMarcador.observe(marcadorDOM);
}
/**
* Función que pasa a la siguiente página
* @preturn void
*/
function avanzarPagina() {
// Incrementar "paginaActual"
paginaActual = paginaActual + 1;
// Redibujar
renderizar();
}
/**
* Función que retrocedea la página anterior
* @return void
*/
function retrocederPagina() {
// Disminuye "paginaActual"
paginaActual = paginaActual - 1;
// Redibujar
renderizar();
}
/**
* Función que devuelve los datos de la página deseada
* @param {Int) pagina - Número de página
* @return {Array<JSON>}
*/
function obtenerRebanadaDeBaseDeDatos(pagina = 1) {
const corteDeInicio = (paginaActual - 1) * elementosPorPagina;
const corteDeFinal = corteDeInicio + elementosPorPagina;
return baseDeDatos.slice(corteDeInicio, corteDeFinal);
}
/**
* Función que devuelve el número total de páginas disponibles
* @return {Int}
*/
function obtenerPaginasTotales() {
return Math.ceil(baseDeDatos.length / elementosPorPagina);
}
/**
* Función que se encarga de dibujar el nuevo DOM a partir de las variables
* @return void
*/
function renderizar() {
// Obtenemos datos
const rebanadaDatos = obtenerRebanadaDeBaseDeDatos(paginaActual);
//// Dibujamos
// Crear articulos
rebanadaDatos.forEach(function (articulo) {
// Clonar la plantilla
const miArticulo = plantillaArticulo.cloneNode(true);
// Rellenamos los datos del nuevo <article>
const miTitulo = miArticulo.querySelector("#titulo");
miTitulo.textContent = articulo.title;
const miCuerpo = miArticulo.querySelector("#cuerpo");
miCuerpo.textContent = articulo.body;
// Lo insertamos dentro de "listadoArticulosDOM"
listadoArticulosDOM.appendChild(miArticulo);
});
}
// --
// Inicio
// --
renderizar(); // Mostramos la primera página nada más que carge la página
vigilanteDeMarcador();
</script>
</body>
</html>
Espero que te resulten los ejemplos de utilidad y puedas aplicarlo a tus WebApps.
{{ comments.length }} comentarios