Como animações disparadas por rolagem causam CLS
Seu header que se esconde no scroll pode estar reprovando o CLS silenciosamente. Veja o motivo e como corrigir.
Sempre que me pedem uma auditoria de Cumulative Layout Shift, começo, obviamente, pelos shifts na fase de carregamento. Procuro imagens sem dimensões, anúncios injetados e troca de fontes (FOUT).
Mas nem todo CLS acontece durante o carregamento da página. Às vezes, ele é disparado por interações como a rolagem. Esses casos são difíceis de pegar porque o Lighthouse não faz scroll, e você precisa saber exatamente onde olhar.
Última revisão por Arjen Karel em março de 2026
Em uma auditoria recente, adicionei dados de RUM do CoreDash e vi um padrão suspeito. Um elemento #header parecia causar muito mais layout shift que o normal.

O que vemos aqui é um header fixo que se esconde quando o usuário rola para baixo e reaparece quando ele rola para cima. É um padrão que vejo com frequência. Se não for feito da forma correta, causa layout shift a cada mudança de direção no scroll.
Por que shifts disparados por rolagem são diferentes
Normalmente o CLS não acontece após uma interação. O CLS mede layout shifts inesperados — e, após uma interação, é de se esperar que o layout mude. É por isso que a métrica tem um período de tolerância automático de 500 ms. Layout shifts que ocorrem em até 500 milissegundos após uma interação do usuário são excluídos da pontuação do CLS. O navegador gerencia isso ativando e verificando a flag hadRecentInput nesses shifts.
O problema é que isso vale apenas para eventos discretos, como cliques, toques na tela e teclas pressionadas. De acordo com a especificação da Layout Instability, o scroll explicitamente não é uma entrada com exclusão. Gestos de arrastar ou de pinça para zoom também não. Apenas eventos discretos recebem a tolerância de 500 ms.
Isso significa que todo layout shift disparado por um evento de scroll conta para a pontuação do CLS.
Segundo o Web Almanac de 2025, 40% das páginas para celular ainda usam animações sem composição. São animações em propriedades como top, height ou margin que disparam o recálculo de layout a cada frame. Se alguma delas rodar durante o scroll, cada frame contará para o CLS.
O problema do header que se esconde no scroll
Vamos começar pelo problema que encontrei hoje: o 'header que se esconde no scroll'. Um header fixo usa JavaScript para alternar a visibilidade durante a rolagem. A implementação é assim:
/* CSS */
#header {
position: fixed;
top: 0;
transition: top 0.3s ease;
}
/* JavaScript */
window.addEventListener('scroll', () => {
if (scrollingDown) {
header.style.top = '-80px'; // esconde
} else {
header.style.top = '0px'; // mostra
}
}); O layout shift é causado pela combinação de transition: top 0.3s ease com a alteração de header.style.top no JavaScript.
Veja o motivo: top é uma propriedade de layout. Quando o navegador processa uma alteração em top, ele executa todo o pipeline de renderização: Style, Layout, Paint, Composite. A cada frame de animação durante essa transição, o navegador recalcula o layout. Cada recálculo gera uma nova entrada de layout shift.
A cada mudança na direção do scroll, o contador de CLS continua subindo enquanto o usuário interagir com a página.
Criei uma demonstração reproduzindo exatamente esse comportamento a partir de um site em produção que eu estava auditando e gravei um vídeo. O CLS total passou de 0,1 depois de um pouco de rolagem. É verdade que esse nível de scroll raramente acontece na prática, mas a mecânica não muda. E já vi esse layout shift empurrar sites para além do limite e reprovar o CLS total.
Quais propriedades CSS podem causar layout shifts quando animadas?
Para uma animação causar CLS, ela precisa disparar o Layout. O pipeline de renderização do navegador tem três etapas principais após o cálculo de estilo: Layout, Paint e Composite. Propriedades que afetam apenas a etapa de Composite nunca causam layout shifts. Para saber mais sobre como transições CSS causam layout shifts, veja o artigo complementar.
Propriedades que disparam layout (e causam CLS quando animadas):
top, left, right, bottom, width, height, margin, padding, border-width, font-size
Todas elas alteram a geometria ou a posição de um elemento. O navegador precisa recalcular a posição desse elemento (e possivelmente de outros elementos também).
Propriedades que ignoram o layout completamente (e são seguras para animação):
transform e opacity rodam na thread do compositor. Elas ignoram tanto o layout quanto o paint. O navegador processa essas propriedades na GPU (é por isso que essas animações funcionam mesmo quando a main thread está ocupada). Como nunca disparam o recálculo de layout, elas jamais geram entradas de layout shift.
Substitua propriedades de layout por transforms
A solução para o header que se esconde no scroll é simples. Substitua a animação de top por transform: translateY().
/* CSS - ANTES (causa CLS) */
#header {
position: fixed;
top: 0;
transition: top 0.3s ease;
}
/* CSS - DEPOIS (sem CLS) */
#header {
position: fixed;
top: 0;
transition: transform 0.3s ease;
}
/* JavaScript - ANTES */
header.style.top = '-80px';
/* JavaScript - DEPOIS */
header.style.transform = 'translateY(-80px)'; O resultado é o mesmo: o header desliza para cima e desaparece. Mas transform: translateY() roda inteiramente no compositor e nunca causa layout shift.
O mesmo princípio vale para qualquer animação disparada por scroll:
- Painéis deslizantes: use
transform: translateX()em vez deleftouright - Elementos expansíveis: use
transform: scaleY()em vez deheight - Elementos com fade: use
opacityem vez devisibilitycombinada com alterações de altura
Outros padrões disparados por scroll que causam CLS
O header que se esconde no scroll é algo que vejo com frequência, mas não é o único. Qualquer JavaScript que altere uma propriedade de layout em resposta a eventos de scroll vai produzir layout shift. Por exemplo:
Efeitos de parallax usando top ou margin-top. Substitua por transform: translateY() ou use a abordagem com perspective e translateZ do CSS para criar parallax apenas com CSS.
Navegação sticky que muda de altura no scroll. Uma barra de navegação que encolhe de 80px para 50px quando o usuário rola a página além de um limite. Se a alteração de altura for animada com transition: height, substitua por transform: scaleY().
Barras de progresso vinculadas ao scroll usando width. Um indicador de progresso de leitura que cresce da esquerda para a direita animando a propriedade width. Em vez disso, use transform: scaleX() com transform-origin: left.
Alternativa moderna: CSS Scroll-Driven Animations. A API animation-timeline: scroll() permite controlar animações com base no progresso do scroll sem usar nada de JavaScript. Como essas animações usam transform e opacity por padrão e rodam no compositor, elas nunca causam layout shifts. O suporte nos navegadores cobre o Chrome 115+ e o Edge 115+, com o Safari ainda em desenvolvimento. Para efeitos atrelados ao scroll, como parallax, barras de progresso e animações de revelação, essa é a solução mais limpa. Se você quer abandonar o scroll via JavaScript de vez, a API de scroll-driven animations é o melhor caminho.
Por que o Lighthouse não detecta isso
O Lighthouse executa um carregamento simulado da página. Ele não rola a página. Não interage com ela após o carregamento. Isso significa que layout shifts disparados por scroll ficam completamente invisíveis em testes de laboratório.
A única forma de pegar esses shifts é com Real User Monitoring. O field data captura todo o ciclo de vida da página, incluindo cada layout shift que ocorre enquanto o usuário rola, clica e navega. Se o seu CLS no CrUX estiver pior que o CLS no Lighthouse, animações disparadas por scroll são uma das primeiras coisas a investigar. O CoreDash monitora o CLS de visitantes reais, incluindo cada shift causado pela rolagem, para você ver exatamente quais elementos são os responsáveis.
Como encontrar layout shifts disparados por scroll
Abra o Chrome DevTools. Vá até o painel Performance. Inicie a gravação (o ícone de círculo) e role a página para cima e para baixo várias vezes. Pare a gravação. Procure pelos losangos roxos na faixa Layout Shifts. Se vir uma concentração de shifts alinhada à sua atividade de rolagem, você tem um problema de CLS disparado por scroll. Você também pode usar a extensão Core Web Vitals Visualizer do Chrome para ver o CLS em tempo real enquanto rola a página.

A regra prática
Se move no scroll, anime com transform. Se tem fade no scroll, anime com opacity. Essas propriedades rodam exclusivamente no compositor.
No JavaScript: revise os handlers de eventos de scroll, callbacks de IntersectionObserver e qualquer código que altere estilos de elementos em resposta à posição da rolagem. Se encontrar top, left, margin, width, height ou padding, substitua pelo transform equivalente.

Precisa de um site mais rápido?
Sou o Arjen e há 17 anos conserto sites lentos. Se você está com problemas que não consegue resolver sozinho, me envie sua URL e vamos conversar.
Me envie sua URL
Por que nosso fix não está funcionando?
Às vezes a CDN ainda entrega o HTML antigo, ou o fix entrou no template de desktop e o celular usa outro. Me envie sua URL e o problema, e eu respondo com alguns horários. Primeiro analiso o field data, depois dedicamos uma hora ao seu site, e em até um dia você tem minhas anotações por escrito. €350 sem IVA.
Me envie sua URLSem desenvolvedor para fazer o fix? Eu mesmo faço o trabalho, em uma cópia do seu site.