JavaScript crear paginador con scroll infinito | Programador Web Valencia

JavaScript crear paginador con scroll infinito

6 minutos

Javascript

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.

Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.

Atribución/Reconocimiento-NoComercial-SinDerivados 4.0 Internacional

¿Me ayudas?

Comprame un café
Pulsa sobre la imagen

No te sientas obligado a realizar una donación, pero cada aportación mantiene el sitio en activo logrando que continúe existiendo y sea accesible para otras personas. Además me motiva a crear nuevo contenido.

Comentarios

{{ comments.length }} comentarios

Nuevo comentario

Nueva replica  {{ formatEllipsisAuthor(replyComment.author) }}

Acepto la política de Protección de Datos.

Escribe el primer comentario

Tal vez también te interese...