Back

Gerenciando Eventos de Scroll Sem Prejudicar a Performance

Gerenciando Eventos de Scroll Sem Prejudicar a Performance

Eventos de scroll são disparados dezenas de vezes por segundo. Sem o tratamento adequado, eles irão destruir a performance do seu site, drenar baterias de dispositivos móveis e criar experiências de usuário travadas. Veja como otimizar handlers de scroll usando throttling, debouncing e passive listeners.

Principais Pontos

  • Throttling limita a execução de funções a intervalos fixos para atualizações consistentes
  • Debouncing aguarda até que o scroll pare antes de executar operações custosas
  • Passive listeners habilitam otimizações imediatas do navegador
  • Intersection Observer elimina eventos de scroll para detecção de visibilidade

O Problema: Por Que Eventos de Scroll Brutos Destroem a Performance

Cada movimento de scroll dispara múltiplos eventos—frequentemente 60+ por segundo. Quando você anexa computações pesadas a esses eventos, você está pedindo para o navegador:

  • Bloquear a thread principal repetidamente
  • Impedir scroll suave
  • Aumentar drasticamente o uso de CPU
  • Drenar a bateria em dispositivos móveis
// NÃO FAÇA ISSO - dispara constantemente
window.addEventListener('scroll', () => {
  calculateExpensiveAnimation();
  updateNavigationState();
  checkElementVisibility();
});

Solução 1: Throttle para Atualizações Consistentes

Throttling limita a execução de funções a um intervalo fixo. Perfeito para animações baseadas em scroll ou indicadores de progresso que precisam de atualizações regulares.

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

// Dispara no máximo a cada 100ms
window.addEventListener('scroll', throttle(() => {
  updateScrollProgress();
}, 100));

Quando usar throttling:

  • Barras de progresso de scroll
  • Efeitos parallax
  • Atualizações de estado de navegação
  • Rastreamento de posição em tempo real

Solução 2: Debounce para Valores Finais

Debouncing aguarda até que o scroll pare antes de executar. Ideal para operações custosas que precisam apenas da posição final do scroll.

function debounce(func, wait) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), wait);
  };
}

// Dispara 200ms após o scroll parar
window.addEventListener('scroll', debounce(() => {
  saveScrollPosition();
  loadMoreContent();
}, 200));

Quando usar debouncing:

  • Triggers de scroll infinito
  • Rastreamento de analytics
  • Salvamento de posição de scroll
  • Cálculos pesados do DOM

Solução 3: Passive Listeners para Performance Instantânea

Passive listeners informam ao navegador que você não chamará preventDefault(), habilitando otimizações imediatas de scroll.

// O navegador pode otimizar o scroll imediatamente
window.addEventListener('scroll', handleScroll, { passive: true });

Esta simples flag melhora a performance do scroll permitindo que o navegador pule a verificação se você vai prevenir o comportamento padrão. Navegadores móveis se beneficiam especialmente desta otimização.

Combinando Técnicas para Performance Máxima

Para interações complexas de scroll, combine múltiplas abordagens:

// Handler com throttle e passive listener
const optimizedScroll = throttle(() => {
  requestAnimationFrame(() => {
    updateUI();
  });
}, 16); // ~60fps

window.addEventListener('scroll', optimizedScroll, { passive: true });

Alternativa Moderna: Intersection Observer

Para detecção de visibilidade, pule eventos de scroll completamente:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      lazyLoadImage(entry.target);
    }
  });
});

observer.observe(document.querySelector('.lazy-image'));

Intersection Observer gerencia detecção de visibilidade sem nenhum listener de scroll, oferecendo performance superior para lazy loading e animações disparadas por scroll.

Guia de Decisão Rápida

TécnicaCaso de UsoFrequência de Atualização
ThrottleBarras de progresso, parallaxIntervalos fixos
DebounceSalvar estado, carregar conteúdoApós o scroll parar
PassiveQualquer handler de scrollSempre (quando possível)
Intersection ObserverDetecção de visibilidadeSem eventos de scroll

Dicas de Implementação

  1. Sempre use passive listeners a menos que você precise de preventDefault()
  2. Comece com delays de throttle de 16ms para animações a 60fps
  3. Use delays de debounce de 200-300ms para ações disparadas pelo usuário
  4. Considere Lodash para implementações testadas em batalha
  5. Profile com Chrome DevTools para medir ganhos reais de performance

Conclusão

Handlers de scroll não otimizados são assassinos de performance. Throttling oferece atualizações controladas para animações, debouncing gerencia valores finais eficientemente, e passive listeners fornecem otimizações instantâneas do navegador. Para detecção de visibilidade, pule eventos de scroll completamente com Intersection Observer. Escolha a técnica certa para seu caso de uso, e seus usuários agradecerão com a vida útil da bateria.

FAQs

Throttling executa sua função em intervalos regulares durante o scroll, como a cada 100ms. Debouncing aguarda até que o scroll pare completamente, então executa uma vez. Use throttling para atualizações contínuas e debouncing para valores finais.

Não, passive listeners explicitamente informam ao navegador que você não chamará preventDefault. Se você precisa prevenir o comportamento padrão de scroll, defina passive como false, mas isso impacta a performance. Considere abordagens alternativas primeiro.

Use Intersection Observer para qualquer lógica baseada em visibilidade como lazy loading, triggers de scroll infinito, ou animações no scroll. É mais performático que eventos de scroll e automaticamente gerencia cálculos de viewport sem verificação manual de posição.

Gain control over your UX

See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay