W3docs

Rolagem em JavaScript

Eventos de rolagem em JavaScript permitem interagir com a rolagem de uma página, sendo úteis para lazy loading e muito mais.

Entendendo Eventos e Técnicas de Rolagem em JavaScript

Eventos de rolagem (events) em JavaScript permitem reagir à posição do usuário em uma página. Essa é a base para recursos como efeitos parallax, acionamento de animações com base na posição de rolagem, implementação de scroll infinito, botões "voltar ao topo", cabeçalhos fixos e barras de progresso de leitura. Este guia abrange como ler a posição atual de rolagem, como rolar programaticamente, como lidar com o evento scroll de forma eficiente e quando usar o moderno IntersectionObserver.

Lendo a Posição Atual de Rolagem

Antes de reagir à rolagem, geralmente é necessário saber até onde a página foi rolada. As propriedades mais confiáveis estão no window:

  • window.scrollY — distância de rolagem vertical em pixels (também window.pageYOffset, um alias mais antigo).
  • window.scrollX — distância de rolagem horizontal em pixels (alias window.pageXOffset).

Para um elemento rolável específico, use element.scrollTop e element.scrollLeft. Essas propriedades são de leitura/escrita: atribuir um valor a elas move o elemento para aquela posição.

// How far down the whole page has the user scrolled?
console.log(window.scrollY); // e.g. 0 at the top, 420 partway down

// Total scrollable height of the document
const docHeight = document.documentElement.scrollHeight;
const winHeight = window.innerHeight;

// How close to the bottom are we (0 = top, 1 = bottom)?
const progress = window.scrollY / (docHeight - winHeight);
console.log(progress);

Para deslocamentos por elemento e uma análise mais aprofundada de getBoundingClientRect, veja Coordenadas em JavaScript e Tamanhos de Janela e Rolagem.

Rolando Programaticamente

Você não apenas escuta eventos de rolagem — também pode acioná-los. Esses métodos aceitam a opção behavior: 'smooth' para uma transição animada em vez de um salto instantâneo:

  • window.scrollTo(x, y) — rola para uma posição absoluta.
  • window.scrollBy(dx, dy) — rola uma quantidade relativa a partir da posição atual.
  • element.scrollIntoView(options) — rola para que um elemento específico fique visível.
// Jump to the very top, smoothly
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });

// Nudge down by one viewport height
window.scrollBy({ top: window.innerHeight, behavior: 'smooth' });

// Bring an element into view (e.g. after navigating to an anchor)
document.querySelector('#section-2')
  .scrollIntoView({ behavior: 'smooth', block: 'start' });

Esta é a forma correta de implementar um botão "voltar ao topo" ou navegação suave por âncoras na página. Use querySelector para selecionar o elemento de destino primeiro.

O Evento de Rolagem em JavaScript

O evento scroll é disparado quando a visualização do documento ou um elemento rolável é rolado. É um dos eventos mais usados para designs dinâmicos e interativos.

Conceitos Principais

  • Frequência do Evento: O evento scroll pode ser disparado dezenas de vezes por segundo, portanto seu handler é executado com muita frequência. Realizar operações pesadas (leituras de layout, escritas no DOM, chamadas de rede) a cada disparo causa rolagem com travamentos. A solução padrão é throttle ou debounce do handler.
  • Rolagem em window vs. em elemento: Você pode escutar o scroll em toda a janela (window.addEventListener('scroll', ...)) ou em um elemento específico com overflow rolável (el.addEventListener('scroll', ...)).
  • scroll não faz bubbling: Um evento scroll em um elemento não sobe até o documento, portanto anexe o listener ao elemento que realmente rola.

Throttle vs. Debounce

Ambos limitam a frequência com que seu handler é executado, mas se comportam de forma diferente:

  • Debounce aguarda até que a rolagem pare por wait ms e então executa uma vez. Bom para "fazer algo quando o usuário terminar de rolar" (ex.: salvar a posição de rolagem).
  • Throttle executa no máximo uma vez a cada wait ms durante a rolagem. Melhor para efeitos contínuos como barras de progresso, onde você quer atualizações regulares durante a rolagem.
function debounce(func, wait) {
  let timeout;
  return function (...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), wait);
  };
}

function throttle(func, wait) {
  let last = 0;
  return function (...args) {
    const now = Date.now();
    if (now - last >= wait) {
      last = now;
      func.apply(this, args);
    }
  };
}

// Debounce: runs once 100ms after scrolling stops
window.addEventListener('scroll', debounce(() => {
  // handle scroll logic here
}, 100));

// Throttle: runs at most every 100ms while scrolling
window.addEventListener('scroll', throttle(() => {
  // update a progress bar, etc.
}, 100));

Ambos os helpers baseados em setTimeout utilizam as APIs de agendamento setTimeout / setInterval.

Exemplos Práticos de Manipulação do Evento de Rolagem

Exemplo 1: Mostrar/Ocultar Navegação ao Rolar

Este exemplo demonstra como ocultar uma barra de navegação ao rolar para baixo e mostrá-la ao rolar para cima, que é um padrão comum em muitos sites modernos para maximizar o espaço da tela.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Scroll Event Navigation Example</title>
    <style>
      #navbar {
        position: fixed;
        top: 0;
        width: 100%;
        background-color: #333;
        color: white;
        text-align: center;
        padding: 10px;
        transition: top 0.3s;
      }
      body {
        padding: 0;
        margin: 0;
        height: 1500px; /* to ensure scrolling */
        font-family: Arial, sans-serif;
      }
    </style>
  </head>
  <body>
    <p style="display: flex; justify-content: center; align-items: center; margin-top: 50vh;"><strong>When you scroll down, the navigation bar disappears. Scroll back up, and it reappears!</strong></p>

    <div id="navbar">Navigation Bar</div>

    <script>
      let lastScrollTop = 0;
      window.addEventListener(
        "scroll",
        function () {
          let currentScroll = window.pageYOffset || document.documentElement.scrollTop;
          if (currentScroll > lastScrollTop) {
            document.getElementById("navbar").style.top = "-50px"; // Adjust based on nav height
          } else {
            document.getElementById("navbar").style.top = "0px";
          }
          lastScrollTop = currentScroll <= 0 ? 0 : currentScroll; // For Mobile or negative scrolling
        },
        false
      );
    </script>
  </body>
</html>

Explicação:

  • O script rastreia a última posição de rolagem e a compara com a posição atual. Se a posição atual for maior, significa que o usuário está rolando para baixo e a barra de navegação é ocultada ajustando sua posição superior para fora da tela.
  • Ao rolar para cima, a barra de navegação reaparece.

Exemplo 2: Acionamento de Animação ao Rolar

Este exemplo mostra como acionar animações quando elementos entram no viewport durante a rolagem.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Scroll Animation Trigger</title>
    <style>
        .box {
            width: 100px;
            height: 100px;
            background: red;
            opacity: 0;
            transition: opacity 2s;
            margin: 600px auto;  /* Ensures it starts out of view */
        }
    </style>
</head>
<body>
<p>Keep scrolling down to see the animation!</p>
<div class="box"></div>

<script>
    let hasAnimated = false;
    window.addEventListener('scroll', function() {
        const box = document.querySelector('.box');
        const rect = box.getBoundingClientRect();
        
        if (rect.top < window.innerHeight && !hasAnimated) {
            box.style.opacity = 1;  // Fade in the box when it comes into view
            hasAnimated = true;
        }
    });
</script>
</body>
</html>

Explicação:

  • Verificação de Visibilidade: O script verifica se o topo do elemento .box está dentro do viewport e altera sua opacidade para 1, acionando um efeito de fade-in. Uma flag impede que a animação seja acionada novamente em eventos de rolagem subsequentes.

Exemplo 3: Efeito Parallax de Rolagem

Este exemplo demonstra um efeito parallax simples onde a imagem de fundo se move em uma velocidade diferente do conteúdo em primeiro plano à medida que você rola a página para baixo.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Enhanced Parallax Scrolling</title>
    <style>
        body, html {
            height: 100%;
            margin: 0;
            font-family: Arial, sans-serif;
            overflow-x: hidden; /* Prevent horizontal scroll */
        }
        .parallax {
            height: 100vh; /* Full height of the viewport */
            position: relative;
            background: url('https://via.placeholder.com/1920x1080') no-repeat center center; 
            background-size: cover;
            display: flex;
            justify-content: center;
            align-items: center;
            color: white;
            font-size: 36px;
            letter-spacing: 1px;
        }
        .content {
            height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: white;
            color: #333;
            font-size: 24px;
            padding: 0 20px;
            text-align: center;
            box-sizing: border-box;
            border-top: 1px solid #ccc;
            border-bottom: 1px solid #ccc;
        }
    </style>
</head>
<body>

<div class="content">Scroll down to see the parallax effect!</div>
<div class="parallax">Stunning Parallax!</div>
<div class="content">Keep scrolling to see more effects.</div>
<div class="parallax"></div>
<div class="content">You have reached the end. Amazing, right?</div>

<script>
    document.addEventListener('scroll', function() {
        document.querySelectorAll('.parallax').forEach(function(el) {
            const factor = 0.5; // Change this for more or less parallax
            const offset = window.pageYOffset * factor - 300; // Adjusts the starting position of background
            el.style.backgroundPositionY = offset + 'px';
        });
    });
</script>

</body>
</html>

Explicação:

  1. Estilos CSS: A classe .parallax define a imagem de fundo para preencher o contêiner e centralizá-la. O exemplo depende inteiramente de JavaScript para o posicionamento, evitando o CSS background-attachment: fixed, que pode causar problemas de desempenho em dispositivos móveis.
  2. Funcionalidade JavaScript: Ao rolar, o script calcula uma nova posição vertical para a imagem de fundo a partir do deslocamento de rolagem. Ao ajustar backgroundPositionY dinamicamente, a imagem se desloca em uma taxa diferente do conteúdo da página, criando o efeito de profundidade parallax.

Em resumo, à medida que você rola, as imagens de fundo se movem mais devagar que o texto, fazendo com que pareçam estar em uma profundidade diferente.

A Alternativa Moderna: IntersectionObserver

Para o caso comum de "fazer algo quando um elemento se tornar visível" (lazy loading, animações de fade-in, scroll infinito), IntersectionObserver é a abordagem moderna recomendada. Em vez de executar seu código a cada evento scroll e ler posições manualmente, o navegador informa de forma assíncrona quando um elemento cruza um limiar — muito mais eficiente e sem travamentos.

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        entry.target.classList.add('visible'); // run once it enters view
        observer.unobserve(entry.target);      // stop watching it
      }
    });
  },
  { threshold: 0.25 } // fire when 25% of the element is visible
);

document.querySelectorAll('.box').forEach((el) => observer.observe(el));

Use o evento scroll quando precisar de uma leitura contínua da posição de rolagem (parallax, barras de progresso). Use IntersectionObserver quando só se importar que um elemento entrou ou saiu do viewport. É conceitualmente similar à API MutationObserver.

Prevenindo e Restaurando a Rolagem

Um requisito comum de UI é bloquear a rolagem da página enquanto um modal ou menu está aberto. Não é possível cancelar o evento scroll com preventDefault() de forma confiável (ele é disparado depois que a rolagem ocorreu). Em vez disso, alterne o CSS overflow:

// Lock scrolling (e.g. when opening a modal)
document.body.style.overflow = 'hidden';

// Restore it when the modal closes
document.body.style.overflow = '';

Conclusão

Lidar com eventos de rolagem de forma eficaz é uma habilidade essencial para desenvolvedores web, possibilitando sites mais interativos e com desempenho otimizado. Seja implementando melhorias de interface de usuário como barras de navegação dinâmicas ou usando um efeito parallax na sua página, entender e manipular corretamente a rolagem em JavaScript pode melhorar drasticamente a experiência do usuário.

Prática

Prática
Quais ações são usos adequados do evento de rolagem JavaScript no desenvolvimento web?
Quais ações são usos adequados do evento de rolagem JavaScript no desenvolvimento web?
Was this page helpful?