O que é Rolagem Infinita?
A rolagem infinita é um recurso usado para carregar dinamicamente mais conteúdo em uma página quando o usuário rola até o final da página.
O conceito de rolagem infinita é usado para carregar dados de um servidor de uma maneira que pareça “sem problemas” para um usuário, mas não sobrecarrega o servidor solicitando muitos dados de uma só vez.
Em um tutorial anterior, implementamos um recurso de paginação o que nos permitiu dividir nosso conteúdo em seções navegáveis conhecidas como páginas. Este tutorial usará uma implementação semelhante.
Benefícios do JavaScript Vanilla
Um benefício significativo de usar JavaScript é que nossa implementação é agnóstica de framework, ou seja, não depende de nenhum framework, então pode ser modificada para funcionar em todos eles.
Além disso, como estamos construindo o recurso nós mesmos e não dependendo de um plugin, podemos garantir que a implementação seja leve e perfeitamente adequada às nossas necessidades.
Aqui está uma olhada no produto final, role até a parte inferior da caneta para carregar mais conteúdo:
1. Marcação com HTML
Começaremos colocando o recipiente para nossos cartões na página. Adicionaremos os cartões ao contêiner usando JavaScript para que o div fique vazio.
<div id="card-container"></div>
Também temos carregador div
para exibir uma animação antes de adicionar o próximo lote de cartões e um cartão de ações div
para mostrar a contagem de cartas e o total de cartas.
<div id="loader"> <div class="skeleton-card"></div> <div class="skeleton-card"></div> <div class="skeleton-card"></div> </div> <div class="card-actions"> <span>Showing <span id="card-count"></span> of <span id="card-total"></span> cards </span> </div>
2. Estilizando com CSS
Os cartões que adicionaremos ao div do contêiner de cartão terão um nome de classe de ‘card’.
#card-container { display: flex; flex-wrap: wrap; } .card { height: 55vh; width: calc((100% / 3) - 16px); margin: 8px; border-radius: 3px; transition: all 200ms ease-in-out; display: flex; align-items: center; justify-content: center; } .card:hover { box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); } .card-actions { margin: 8px; padding: 16px 0; display: flex; justify-content: space-between; align-items: center; }
Também criaremos uma animação de carregamento para os cartões de esqueleto na div do carregador, animando o ::after
pseudo-seletor:
#loader { display: flex; } .skeleton-card { height: 55vh; width: calc((100% / 3) - 16px); margin: 8px; border-radius: 3px; transition: all 200ms ease-in-out; position: relative; background-color: #eaeaea; } .skeleton-card::after { content: ""; position: absolute; top: 0; right: 0; bottom: 0; left: 0; transform: translateX(-100%); background-image: linear-gradient(90deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.2) 20%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0)); animation: load 1s infinite; } @keyframes load { 100% { transform: translateX(100%); } }
Estilo acessível
Sempre que incluímos uma animação em uma página da Web, é importante considerar as implicações de acessibilidade. Alguns usuários podem preferir não ter nenhuma animação e podemos levar essa preferência em consideração em nosso estilo usando a regra de mídia, prefers-reduced-motion
@media screen and (prefers-reduced-motion: reduce) { .skeleton-card::after { animation: none; } }
3. Funcionalidade com JavaScript
Vamos quebrar a lógica por trás da rolagem infinita.
- Defina o limite do conteúdo a ser carregado na página.
- Detecte quando o usuário atingiu o final do contêiner de conteúdo.
- Carregue mais conteúdo quando o final do contêiner for atingido.
- Se não houver mais conteúdo a ser carregado, pare a rolagem infinita.
Definindo Constantes
Primeiro, vamos pegar todos os elementos que precisaremos do nosso DOM:
const cardContainer = document.getElementById("card-container"); const cardCountElem = document.getElementById("card-count"); const cardTotalElem = document.getElementById("card-total"); const loader = document.getElementById("loader");
Agora precisamos definir nossas variáveis globais.
Precisaremos de um valor para o número máximo de cartões a serem adicionados à página. Se você estiver obtendo seus dados de um servidor, esse valor será o comprimento da resposta do servidor. Vamos inicializar um limite de cartão de 99.
const cardLimit = 99;
o cardTotalElem
é o elemento para exibir o número máximo de cartões na página para que possamos definir o innerHTML
para o cardLimit
valor;
cardTotalElem.innerHTML = cardLimit;
Em seguida, definiremos uma variável para quantos cartões queremos aumentar a página:
const cardIncrease = 9;
Queremos saber quantas “páginas” teremos, ou seja, quantas vezes podemos aumentar o conteúdo até atingirmos o limite máximo. Por exemplo, com nosso definido cardLimit
e cardIncrease
variáveis, podemos aumentar o conteúdo 10 vezes (assumindo que já carregamos os primeiros 9 elementos) até atingirmos o limite. Faremos isso dividindo o cardLimit
pelo cardIncrease
.
const pageCount = Math.ceil(cardLimit / cardIncrease);
Em seguida, definiremos um valor para determinar em qual página estamos:
let currentPage = 1;
Criando um novo cartão
Agora que temos todas as nossas constantes, vamos fazer uma função para adicionar um novo cartão ao container de cartões. Vamos definir o innerHTML
dos nossos cartões ao valor do índice para que possamos acompanhar o número de cartões que estamos adicionando.
Um recurso divertido nesta demonstração é que cada cartão tem uma cor de fundo gerada aleatoriamente.
const getRandomColor = () => { const h = Math.floor(Math.random() * 360); return `hsl(${h}deg, 90%, 85%)`; }; const createCard = (index) => { const card = document.createElement("div"); card.className = "card"; card.innerHTML = index; card.style.backgroundColor = getRandomColor(); cardContainer.appendChild(card); };
Adicionando cartões ao contêiner
Agora adicionaremos nossos cartões ao nosso contêiner usando uma funcionalidade semelhante a o tutorial de paginação.
Primeiro, determine o intervalo de cartões a serem adicionados à página. o addCards
função aceitará um pageIndex
parâmetro, que irá atualizar o global currentPage
valor. Se estivermos na página 1, adicionaremos os cartões de 1 a 9. Se estivermos na página 2, adicionaremos os cartões de 10 a 18 e assim por diante.
Podemos definir isso matematicamente como:
const addCards = (pageIndex) => { currentPage = pageIndex; const startRange = (pageIndex - 1) * cardIncrease; const endRange = pageIndex * cardIncrease; for (let i = startRange + 1; i <= currRange; i++) { createCard(i); } };
Nesta função, nosso intervalo inicial será sempre um a menos do que o valor que estamos tentando obter (ou seja, na página 1, o intervalo inicial é 0, na página 2, o intervalo inicial é 9), portanto, consideraremos isso definindo o valor do nosso índice de loop for para startRange + 1
.
Detectando quando o limite do cartão é atingido
Um limite que teremos que observar é o endRange
número. Se estivermos na última página, queremos que nosso intervalo final seja o mesmo que o cardLimit
. Por exemplo, se tivermos um cardLimit
de 75 e um cardIncrease
de 10 e estamos na página 8, nosso índice inicial será 70 e nosso endRange
valor deve ser 75.
Vamos modificar nosso addCards
função para dar conta disso:
const addCards = (pageIndex) => { currentPage = pageIndex; const startRange = (pageIndex - 1) * cardIncrease; const endRange = currentPage == pageCount ? cardLimit : pageIndex * cardIncrease; for (let i = startRange + 1; i <= endRange; i++) { createCard(i); } };
Nossa demonstração também inclui um cardTotal
elemento que exibe o número de cartões que estão sendo mostrados na página, então definiremos o innerHTML
deste elemento como o intervalo final.
const addCards = (pageIndex) => { currentPage = pageIndex; const startRange = (pageIndex - 1) * cardIncrease; const endRange = currentPage == pageCount ? cardLimit : pageIndex * cardIncrease; cardCountElem.innerHTML = endRange; for (let i = startRange + 1; i <= endRange; i++) { createCard(i); } };
Carregando cartões iniciais
Definimos um recurso para adicionar cartões ao contêiner para incluir um window.onload
função para definir os cartões iniciais a serem adicionados à página.
window.onload = function () { addCards(currentPage); };
Manipulando a rolagem infinita
Vamos lidar com nossa rolagem infinita aumentando o currentPage
número para adicionar novos cartões ao contêiner quando chegarmos ao final da página. Podemos detectar quando o final da página é alcançado adicionando o innerHeight
da janela para o valor de rolagem pageYOffset
e comparando com o documento offsetHeight
que é a altura total da página.
Aqui está uma representação visual de como isso se parece:
Quando chegamos ao final da página, queremos carregar uma nova página chamando nosso addCards
função com currentPage + 1.
const handleInfiniteScroll = () => { const endOfPage = window.innerHeight + window.pageYOffset >= document.body.offsetHeight; if (endOfPage) { addCards(currentPage + 1); } };
Em seguida, criamos um ouvinte de eventos para a rolagem da janela e passamos nossa função acima para ele:
window.addEventListener("scroll", handleInfiniteScroll);
Otimização de desempenho
Como estamos trabalhando com o ouvinte de eventos de rolagem, é benéfico para o desempenho de nossa página da Web limitar o número de chamadas feitas. Podemos diminuir o número de chamadas usando uma função de aceleração.
Vamos definir nossa função de aceleração desta forma:
var throttleTimer; const throttle = (callback, time) => { if (throttleTimer) return; throttleTimer = true; setTimeout(() => { callback(); throttleTimer = false; }, time); };
e então passamos a função do acelerador para o handleInfiniteScroll
função
const handleInfiniteScroll = () => { throttle(() => { const endOfPage = window.innerHeight + window.pageYOffset >= document.body.offsetHeight; if (endOfPage) { addCards(currentPage + 1); } }, 1000); };
Parando a rolagem infinita
Neste ponto, configuramos nossas funções para adicionar mais conteúdo quando o final da página for atingido. Agora, vamos garantir que nossa função pare de ser executada quando não houver mais conteúdo a ser adicionado, ou seja, quando o cardLimit
é atingido.
Primeiro, vamos definir nosso removeInfiniteScroll
função. Nesta função, vamos remover o handleInfiniteScroll
função do ouvinte de eventos de rolagem e também exclua o div do carregador.
const removeInfiniteScroll = () => { loader.remove(); window.removeEventListener("scroll", handleInfiniteScroll); };
Agora vamos modificar nosso handleInfiniteScroll
para contabilizar se não houver mais conteúdo a ser adicionado, ou seja, estamos na última página de conteúdo.
const handleInfiniteScroll = () => { throttle(() => { const endOfPage = window.innerHeight + window.pageYOffset >= document.body.offsetHeight; if (endOfPage) { addCards(currentPage + 1); } if (currentPage === pageCount) { removeInfiniteScroll(); } }, 1000); };
Conclusão
E aí temos que! Construímos uma implementação acessível e de alto desempenho do recurso de rolagem infinita. Confira o código JavaScript completo clicando no botão JS guia na demonstração incorporada abaixo: