SWCore
SWCore / Componentes / Infinite Scroll

Infinite Scroll Componentes

Quando a pessoa chega perto do fim da lista, ele busca a próxima página no seu servidor e acrescenta embaixo — sem botão "carregar mais".

O que é

É o que o Instagram e os blogs fazem: você rola, e mais posts aparecem sozinhos. Você marca a área com sw-infinite, diz de onde buscar (sw-infinite-url, um endereço do seu site que devolve um pedaço de HTML) e onde colocar (sw-infinite-target). O resto é automático.

Precisa do JavaScript do SWCore e de um endereço no seu servidor que devolva o HTML dos próximos itens. O script vigia a rolagem: quando falta menos que sw-infinite-offset pixels para o fim, ele busca a URL (trocando {page} pelo número da página), mostra "Carregando…" e acrescenta o que veio no alvo. Quando o servidor devolver uma resposta vazia, ele entende que acabou e para. Os componentes SWCore que vierem no HTML novo já chegam funcionando.

Quando usar

  • Blog, notícias, galeria de fotos: a pessoa só quer continuar rolando.
  • Lista longa em painel onde paginar com números atrapalha (histórico, mensagens).
  • Não use em página que tem rodapé importante (contato, endereço, política): ele nunca chega a aparecer. Nesse caso, pagine com números (Paginação) ou um botão "Ver mais".

Comece aqui

  1. 1

    Coloque o SWCore na página (o JS é obrigatório).

    <link rel="stylesheet" href="https://swcore.sanweb.com.br/dist/2.1.0/swcore.min.css">
    
    <!-- fim do body -->
    <script src="https://swcore.sanweb.com.br/dist/2.1.0/swcore.min.js"></script>
  2. 2

    Crie no seu servidor um endereço que devolva só o HTML dos itens de uma página (sem <html>, sem cabeçalho). Ex.: /posts.php?page=2 devolve:

    <article class="post">Post 11</article>
    <article class="post">Post 12</article>
    <article class="post">Post 13</article>
    
    <!-- página que não existe mais: devolva uma resposta VAZIA — é assim que ele sabe que acabou -->
  3. 3

    Na página, marque a lista e diga de onde buscar e onde colocar. Se a página já mostra a página 1, comece a busca pela 2 com sw-infinite-start="2".

    <div sw-infinite
         sw-infinite-url="/posts.php?page={page}"
         sw-infinite-target="#lista-posts"
         sw-infinite-start="2">
      <div id="lista-posts">
        <article class="post">Post 1</article>
        <!-- … os posts da página 1, vindos do servidor … -->
      </div>
    </div>

Todas as variações

Cada quadro abaixo é o componente de verdade, rodando. O código embaixo é exatamente o que está no quadro: copie e cole.

1. Rolando dentro de uma caixa

Estes quadros buscam de verdade um arquivo de exemplo deste site (assets/demo-infinite.html, que sempre devolve 4 posts, então nunca "acaba"). Role até o fim da caixa. Se a própria [sw-infinite] tiver rolagem (aqui, ovy + altura pela proporção ar-21-9), ele usa essa rolagem sozinho. sw-infinite-offset diz com quantos pixels de antecedência ele começa a buscar (padrão 300).

Post 1 — já veio com a página

Post 2 — já veio com a página

Post 3 — já veio com a página

Post 4 — já veio com a página

Post 5 — já veio com a página

Post 6 — já veio com a página

Post 7 — já veio com a página

Post 8 — já veio com a página

Post 9 — já veio com a página

Post 10 — já veio com a página

Post 11 — já veio com a página

Post 12 — já veio com a página

Post 13 — já veio com a página

Post 14 — já veio com a página

sw-infinite-offset="40" — só busca quando você encosta no fim
<div sw-infinite sw-infinite-url="../../assets/demo-infinite.html" sw-infinite-target="#inf-demo-1" sw-infinite-offset="40" class="ovy ar-21-9 bd r-m p-2">
  <div id="inf-demo-1">
    <p>Post 1 — já veio com a página</p>
    <p>Post 2 — já veio com a página</p>
    <p>Post 3 — já veio com a página</p>
    <p>Post 4 — já veio com a página</p>
    <p>Post 5 — já veio com a página</p>
    <p>Post 6 — já veio com a página</p>
    <p>Post 7 — já veio com a página</p>
    <p>Post 8 — já veio com a página</p>
    <p>Post 9 — já veio com a página</p>
    <p>Post 10 — já veio com a página</p>
    <p>Post 11 — já veio com a página</p>
    <p>Post 12 — já veio com a página</p>
    <p>Post 13 — já veio com a página</p>
    <p>Post 14 — já veio com a página</p>
  </div>
</div>

Post 1 — já veio com a página

Post 2 — já veio com a página

Post 3 — já veio com a página

Post 4 — já veio com a página

Post 5 — já veio com a página

Post 6 — já veio com a página

Post 7 — já veio com a página

Post 8 — já veio com a página

Post 9 — já veio com a página

Post 10 — já veio com a página

Post 11 — já veio com a página

Post 12 — já veio com a página

Post 13 — já veio com a página

Post 14 — já veio com a página

Sem offset (300 px) — já busca antes de você chegar no fim
<div sw-infinite sw-infinite-url="../../assets/demo-infinite.html" sw-infinite-target="#inf-demo-2" class="ovy ar-21-9 bd r-m p-2">
  <div id="inf-demo-2">
    <p>Post 1 — já veio com a página</p>
    <p>Post 2 — já veio com a página</p>
    <p>Post 3 — já veio com a página</p>
    <p>Post 4 — já veio com a página</p>
    <p>Post 5 — já veio com a página</p>
    <p>Post 6 — já veio com a página</p>
    <p>Post 7 — já veio com a página</p>
    <p>Post 8 — já veio com a página</p>
    <p>Post 9 — já veio com a página</p>
    <p>Post 10 — já veio com a página</p>
    <p>Post 11 — já veio com a página</p>
    <p>Post 12 — já veio com a página</p>
    <p>Post 13 — já veio com a página</p>
    <p>Post 14 — já veio com a página</p>
  </div>
</div>

Tabela de opções

PalavraO que fazExemplo
Obrigatórios
sw-infiniteMarca a área. Pode ser a própria caixa que rola, ou um bloco dentro da página.<div sw-infinite …>
sw-infinite-urlEndereço que devolve o HTML dos próximos itens. {page} é trocado pelo número da página.sw-infinite-url="/posts.php?page={page}"
sw-infinite-targetOnde o HTML novo entra (seletor CSS). Sem ele, entra no fim do <body> — quase nunca é o que você quer.sw-infinite-target="#lista-posts"
Ajustes
sw-infinite-startNúmero da primeira página que ele busca. Padrão: 1. Se a página já traz a 1, use 2.sw-infinite-start="2"
sw-infinite-trustedPula a limpeza do HTML que chega (tira <script>, onclick="" e links javascript:). Só para conteúdo 100% seu, nunca com texto escrito por usuário.sw-infinite-trusted
sw-infinite-offsetQuantos pixels antes do fim ele começa a buscar. Padrão: 300.sw-infinite-offset="600"
sw-infinite-rootSeletor da caixa que rola, quando não é a própria [sw-infinite] nem a página.sw-infinite-root="#coluna"
Quem rola (ele descobre sozinho)
(sem sw-infinite-root)Se a [sw-infinite] (ou uma caixa acima dela) tem rolagem e está cheia, usa essa. Senão, usa a rolagem da página.
Aparência
.sw-infinite-loaderO aviso "Carregando…" que aparece no fim do alvo durante a busca. Mude o visual no CSS da sua página, se quiser.

Receitas prontas — usos reais

Situações que aparecem em quase todo site. Copie a que servir.

Blog: a página inteira rola Referência
<main>
  <div sw-infinite
       sw-infinite-url="/blog/pagina/{page}"
       sw-infinite-target="#posts"
       sw-infinite-start="2"
       sw-infinite-offset="600">
    <div id="posts">
      <!-- posts da página 1, já vindos do servidor (bom para o Google) -->
    </div>
  </div>
</main>
<!-- a <main> não tem rolagem própria: ele usa a rolagem da página -->
Lado do servidor (PHP): devolver uma página de itens Referência
<?php
// /posts.php?page=2
$porPagina = 10;
$pagina    = max(1, (int) ($_GET['page'] ?? 1));
$offset    = ($pagina - 1) * $porPagina;

$stmt = $pdo->prepare('SELECT titulo, resumo FROM posts ORDER BY id DESC LIMIT ? OFFSET ?');
$stmt->bindValue(1, $porPagina, PDO::PARAM_INT);
$stmt->bindValue(2, $offset, PDO::PARAM_INT);
$stmt->execute();

header('Content-Type: text/html; charset=utf-8');
foreach ($stmt as $p) {
    echo '<article class="post"><h2>' . htmlspecialchars($p['titulo']) . '</h2>'
       . '<p>' . htmlspecialchars($p['resumo']) . '</p></article>';
}
// sem linhas = resposta vazia = o componente para de buscar
Avisar quando acabou e quando deu erro Ao vivo

Post 1 — já veio com a página

Post 2 — já veio com a página

Post 3 — já veio com a página

Post 4 — já veio com a página

Post 5 — já veio com a página

Post 6 — já veio com a página

Post 7 — já veio com a página

Post 8 — já veio com a página

Post 9 — já veio com a página

Post 10 — já veio com a página

Post 11 — já veio com a página

Post 12 — já veio com a página

Post 13 — já veio com a página

Post 14 — já veio com a página

Role a caixa: aqui aparece o que o evento trouxe.

<div sw-infinite id="posts-box" sw-infinite-url="/posts.php?page={page}" sw-infinite-target="#posts">…</div>
<p id="fim-lista" hidden>Você viu tudo.</p>

<script>
  const box = document.getElementById('posts-box');

  box.addEventListener('sw:infinite:load', (e) => {
    // e.detail.items = os elementos que chegaram; e.detail.page = a página que acabou de chegar
    console.log(e.detail.items.length, 'itens novos');
  });

  box.addEventListener('sw:infinite:end', () => {
    document.getElementById('fim-lista').hidden = false;
  });

  box.addEventListener('sw:infinite:error', (e) => {
    SW.Alert.err('Não foi possível carregar a página ' + e.detail.page);
  });
</script>

Eventos

sw:infinite:load
Chegou conteúdo e já foi colocado. Traz e.detail.items (lista dos elementos novos) e e.detail.page (o número da página que acabou de chegar).
sw:infinite:end
O servidor devolveu resposta vazia: acabou, ele não busca mais.
sw:infinite:error
A busca falhou (sem internet, servidor fora). Traz e.detail.page. Ele tenta de novo na próxima rolagem.

API (JavaScript)

SW.Infinite.initAll(raiz)
Liga as áreas [sw-infinite] dentro de raiz. Normalmente não precisa: o SWCore liga sozinho.

Precisa de quê

  • swcore.min.css e swcore.min.js.
  • Um endereço no seu servidor que devolva o HTML dos itens de cada página (texto HTML, não JSON) e uma resposta vazia quando não houver mais.
  • Se o endereço for de outro domínio, ele precisa liberar o seu site (CORS). O mais simples é ficar no mesmo domínio.

Cuidados — erros comuns

  • Esquecer o sw-infinite-target: o HTML novo vai parar no fim do <body>, depois do rodapé.
  • A página já mostra a página 1 e você não pôs sw-infinite-start="2": a primeira busca traz a página 1 de novo e os posts aparecem repetidos.
  • Servidor que devolve a página inteira (com <html>, menu, rodapé): tudo isso é colado na lista. Devolva só os itens.
  • Servidor que nunca devolve vazio (ex.: repete a última página): ele busca para sempre. Na última página, responda sem conteúdo.
  • Caixa com rolagem própria mas sem altura: ela cresce com o conteúdo, não rola, e ele passa a vigiar a rolagem da página.
  • Apontar para um endereço que devolve JSON: ele cola o texto do JSON na tela. Para JSON, use o Conteúdo via JSON.

O que ele não faz

  • Não monta HTML a partir de JSON — o servidor precisa mandar o HTML pronto.
  • O HTML que chega passa por uma limpeza (a mesma do sw-ajax): <script>, atributos on…="" e links javascript: são removidos. Mesmo assim, escape no servidor o texto que o usuário escreveu (como o htmlspecialchars da receita).
  • Não tem botão "carregar mais" nem função para forçar a próxima busca por código: ela acontece pela rolagem.
  • Se as primeiras páginas não enchem a caixa, ele só busca de novo quando houver rolagem. Mande itens suficientes por página para passar da altura da tela.
  • O rodapé da página fica inalcançável enquanto houver páginas para carregar.