Programação Por Equipe DevBras

Capa do artigo: Ação disparada no scroll: o que é ajuda e o que é armadilha para quem lê

Ação disparada no scroll: o que é ajuda e o que é armadilha para quem lê

Como usar a posição da rolagem para carregar imagem, mostrar progresso e paginar sem abrir modal surpresa, travar o fundo, esconder o rodapé ou inflar a métrica de leitura.

O blog de receitas mede quanto cada visitante rolou. Ao passar de 40% da página, abre um modal da newsletter e o fundo trava. A pessoa estava com a mão suja de farinha, no meio do modo de preparo, e teve que caçar um "x" de 12 pixels. A métrica de cadastros subiu um pouco. O tempo na página e o retorno de visitantes caíram bem mais.

A resposta curta: rolar a página não é clique nem consentimento. Use a posição da rolagem para o que ajuda a leitura: carregar imagem perto da hora de aparecer, mostrar onde a pessoa está, buscar mais itens quando ela pedir. Não amarre a um percentual de rolagem nada que interrompa, como modal, trava de fundo, troca de página ou envio de dados. E se medir "chegou ao fim", chame de "chegou ao fim", não de "leu".

Por que o scroll virou gatilho

Descobrir a posição de um elemento é fácil: elemento.getBoundingClientRect().top diz quantos pixels ele está do topo da área visível. Com isso, qualquer coisa pode acontecer "quando a pessoa chegar aqui". A técnica é neutra. O problema é que ela permite agir sem pedir. Um clique em "Assinar" é uma decisão. Rolar até o meio de uma receita é só ler a receita.

O próprio Google tem orientação pública contra intersticiais intrusivos que cobrem o conteúdo no celular. Mesmo sem pensar em busca, a conta é simples: tudo o que interrompe a leitura cobra de quem veio ler.

O que pode depender do scroll

  • Carregar imagem e vídeo abaixo da dobra: economiza dados e acelera a primeira tela.
  • Indicar progresso e posição: barra fina de progresso, item do sumário destacado.
  • Mostrar o botão "voltar ao topo" depois de uma rolagem longa.
  • Pedir mais itens numa lista, desde que o rodapé continue alcançável.

O que não deve depender do scroll

  • Abrir modal, pop-up de newsletter ou pedido de notificação.
  • Travar o fundo com overflow: hidden no body.
  • Marcar algo como "lido", "aceito" ou "visto" com efeito para a pessoa.
  • Esconder conteúdo até a pessoa rolar, de um jeito que some se o script falhar.

Como fazer cada uso legítimo

  1. Para imagens, use primeiro o recurso nativo: loading="lazy" no img. Não precisa de script.
  2. Para saber se um bloco entrou na tela, use IntersectionObserver em vez de ouvir cada evento de rolagem. O guia saber quando um bloco entrou na tela mostra a técnica em detalhe.
  3. Se precisar mesmo do evento scroll, registre com { passive: true } e faça só leitura barata dentro dele.
  4. Para listas longas, ofereça um botão "Carregar mais" que a pessoa aciona.
  5. Respeite prefers-reduced-motion em toda animação ligada à rolagem.

A imagem do passo a passo da receita, sem uma linha de JavaScript:

<img src="/receitas/pao-de-queijo/passo-3.jpg"
     alt="Massa sendo enrolada em bolinhas de 3 cm"
     width="800" height="600"
     loading="lazy">

Largura e altura no HTML reservam o espaço da imagem antes de ela chegar, e o texto não pula quando ela carrega. Não use loading="lazy" na imagem principal do topo: ela precisa vir logo.

Lista que carrega mais sem sequestrar o rodapé

Rolagem infinita automática transforma o rodapé num lugar inalcançável: cada vez que a pessoa chega perto do contato ou da política de privacidade, mais itens empurram tudo para baixo. Um botão resolve, e ainda permite voltar ao ponto certo:

<ol id="lista-receitas">...</ol>
<button type="button" id="carregar-mais" data-pagina="2">Carregar mais receitas</button>

<script>
const botao = document.getElementById('carregar-mais');
const lista = document.getElementById('lista-receitas');

botao.addEventListener('click', async function () {
  botao.disabled = true;
  botao.textContent = 'Carregando...';
  try {
    const pagina = botao.dataset.pagina;
    const resposta = await fetch('/receitas?pagina=' + pagina + '&formato=itens');
    if (!resposta.ok) throw new Error('HTTP ' + resposta.status);
    const antes = lista.children.length;
    lista.insertAdjacentHTML('beforeend', await resposta.text());
    const primeiroNovo = lista.children[antes];
    if (primeiroNovo) {
      primeiroNovo.setAttribute('tabindex', '-1');
      primeiroNovo.focus();
    }
    botao.dataset.pagina = String(Number(pagina) + 1);
    botao.textContent = 'Carregar mais receitas';
  } catch (erro) {
    botao.textContent = 'Falhou. Tentar de novo';
  } finally {
    botao.disabled = false;
  }
});
</script>

O foco vai para o primeiro item novo, então quem usa teclado ou leitor de tela continua de onde parou, em vez de voltar ao começo da lista. Para listas paginadas no servidor, combine com a técnica de paginação que não pula nem repete item.

Métrica de leitura honesta

"Rolou até 100%" não é "leu". A pessoa pode ter descido direto para os comentários. Se a equipe quer saber quantos chegaram ao fim do texto, meça exatamente isso, com um marcador no fim do artigo e um envio que não atrasa a navegação:

const fim = document.getElementById('fim-do-artigo');

const observador = new IntersectionObserver(function (entradas) {
  if (entradas[0].isIntersecting) {
    navigator.sendBeacon('/metricas', JSON.stringify({ evento: 'chegou_ao_fim', pagina: location.pathname }));
    observador.disconnect();
  }
});

observador.observe(fim);

O nome do evento é chegou_ao_fim, não leu_o_artigo. Parece detalhe, mas é o que impede o relatório de prometer algo que o dado não mostra. Se a métrica envolver identificar a pessoa, ela entra nas mesmas regras de consentimento do resto da análise de uso.

Animação de revelar no scroll

Muito tema "moderno" começa com os blocos em opacity: 0 e só os mostra quando entram na tela. Se o script falhar, carregar devagar ou for bloqueado, o conteúdo fica invisível. Faça o contrário: o conteúdo nasce visível e a animação é um enfeite opcional, desligado para quem pediu menos movimento:

@media (prefers-reduced-motion: no-preference) {
  .revelar {
    transition: opacity 0.4s, transform 0.4s;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

Erros comuns

  • Modal amarrado a um percentual: interrompe a leitura no pior momento e ensina a pessoa a sair do site.
  • Travar o fundo e não destravar: se o modal falha ou é fechado por um caminho não previsto, a página fica sem rolagem.
  • Rolagem infinita sem botão: o rodapé, com contato e política, nunca é alcançado.
  • Conteúdo invisível até rolar: com o script bloqueado ou lento, a página aparece vazia.
  • Chamar "rolou" de "leu": o relatório decide pauta com um número que não mede leitura.
  • Ouvir scroll com trabalho pesado: a rolagem engasga no celular, justamente onde a pessoa mais rola.

Checklist

  • Nenhum modal ou pedido de permissão abre só porque a pessoa rolou.
  • O fundo nunca fica travado sem um modal aberto por decisão da pessoa.
  • Imagens abaixo da dobra usam loading="lazy" com largura e altura.
  • Listas longas têm "Carregar mais" e o rodapé é alcançável.
  • O conteúdo aparece com JavaScript desligado.
  • Animações de rolagem respeitam prefers-reduced-motion.
  • A métrica tem o nome do que realmente mede.

Abra o seu site no celular e role de uma ponta à outra sem tocar em nada além da tela: você chega ao rodapé sem fechar nenhuma janela no caminho?

Avalie este artigo

Referência: http://pt.stackoverflow.com/questions/41088/descobrir-posi%C3%A7%C3%A3o-do-scroll-de-um-determinado-objeto