Como as animações desencadeadas por scroll causam CLS

O seu cabeçalho que se esconde no scroll pode estar a falhar o CLS silenciosamente. Eis o motivo e como o corrigir.

Arjen Karel
Atualizado a · 6 min de leitura
Partilhar

Sempre que me pedem uma auditoria ao Cumulative Layout Shift, começo obviamente pelos shifts durante a fase de carregamento. Verifico imagens sem dimensões, anúncios injetados e trocas de fontes (FOUT).

Mas nem todo o CLS acontece durante o carregamento da página. Por vezes, é desencadeado por interações como o scroll. Estes casos são difíceis de apanhar: o Lighthouse não faz scroll e é preciso saber exatamente onde procurar.

Última revisão por Arjen Karel em março de 2026

Numa auditoria recente, adicionei dados RUM do CoreDash e vi um padrão suspeito. Um elemento #header parecia causar muito mais layout shift do que o normal.

O que vemos aqui é um cabeçalho fixo que se esconde quando o utilizador faz scroll para baixo e reaparece ao fazer scroll para cima. É um padrão que vejo com frequência. Se não for bem feito, causa um layout shift a cada mudança de direção do scroll.

Porque é que os layout shifts provocados por scroll 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, é expectável que o layout mude. É por isso que a métrica CLS tem um período de tolerância automático de 500ms. Os layout shifts que ocorrem nos 500 milissegundos após uma interação do utilizador são excluídos da pontuação de CLS. O browser trata disto definindo e verificando uma flag hadRecentInput nestes shifts.

O problema é que isto só se aplica a eventos discretos, como cliques, toques e teclas premidas. De acordo com a especificação da Layout Instability, o scroll não é, explicitamente, um input de exclusão. Nem ações de arrastar nem gestos de pinça. Apenas os eventos discretos têm direito ao período de tolerância de 500ms.

Isto significa que qualquer layout shift desencadeado por um evento de scroll conta para a pontuação de CLS.

De acordo com o Web Almanac de 2025, 40% das páginas mobile continuam a usar animações não compostas. São animações em propriedades como top, height ou margin que acionam o recálculo do layout a cada frame. Se alguma delas correr durante o scroll, cada frame conta para o CLS.

O problema do cabeçalho que se esconde no scroll

Comecemos pelo problema que encontrei hoje: o cabeçalho que se esconde no scroll. Um cabeçalho fixo usa JavaScript para alternar a visibilidade no scroll. A implementação é esta:

/* CSS */
#header {
  position: fixed;
  top: 0;
  transition: top 0.3s ease;
}

/* JavaScript */
window.addEventListener('scroll', () => {
  if (scrollingDown) {
    header.style.top = '-80px';  // ocultar
  } else {
    header.style.top = '0px';    // mostrar
  }
});

O layout shift é causado pela combinação de transition: top 0.3s ease com a alteração de header.style.top em JavaScript.

Eis o motivo: top é uma propriedade de layout. Quando o browser processa uma alteração em top, executa todo o pipeline de renderização: Style, Layout, Paint, Composite. A cada frame de animação durante essa transição, o browser recalcula o layout. Cada recálculo produz uma nova entrada de layout shift.

A cada mudança de direção do scroll, o contador de CLS continua a subir enquanto o utilizador interagir com a página.

Construí uma demo a reproduzir exatamente este comportamento de um site real que estava a auditar e gravei um vídeo. Após algum scroll, o CLS total ultrapassou os 0,1. É certo que este nível de scroll raramente acontece no mundo real, mas o mecanismo é o mesmo. E já vi este layout shift empurrar sites para além do limite e fazer o CLS total falhar.

Que propriedades CSS podem causar layout shifts quando animadas?

Para uma animação causar CLS, precisa de desencadear o Layout. O pipeline de renderização do browser tem três etapas principais após o cálculo do estilo: Layout, Paint e Composite. As propriedades que afetam apenas a etapa Composite nunca causam layout shifts. Para saber mais sobre como as transições CSS causam layout shifts, consulte o artigo complementar.

Propriedades que desencadeiam layout (e causam CLS quando animadas):

top, left, right, bottom, width, height, margin, padding, border-width, font-size

Todas estas propriedades alteram a geometria ou a posição de um elemento. O browser tem de recalcular a posição do elemento (e possivelmente de outros elementos também).

Propriedades que ignoram o layout por completo (e são seguras para animação):

transform e opacity correm na thread do compositor. Ignoram tanto o layout como o paint. O browser processa-as na GPU (é por isso que estas animações funcionam mesmo quando a main thread está ocupada). Como nunca desencadeiam o recálculo do layout, nunca geram entradas de layout shift.

Substitua propriedades de layout por transforms

A correção para o cabeçalho 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 cabeçalho desliza para cima e desaparece. Mas transform: translateY() corre inteiramente no compositor e nunca causa um layout shift.

O mesmo princípio aplica-se a qualquer animação desencadeada por scroll:

  • Painéis deslizantes: use transform: translateX() em vez de left ou right
  • Elementos expansíveis: use transform: scaleY() em vez de height
  • Elementos com fade: use opacity em vez de visibility associada a alterações de altura

Outros padrões provocados por scroll que causam CLS

O cabeçalho que se esconde no scroll é algo que vejo com frequência, mas há mais. Qualquer JavaScript que altere uma propriedade de layout em resposta a eventos de scroll vai produzir um layout shift. Por exemplo:

Efeitos de parallax que usam top ou margin-top. Substitua por transform: translateY() ou use a abordagem de perspective e translateZ para parallax em CSS puro.

Navegação sticky que altera a altura no scroll. Uma barra de navegação que encolhe de 80px para 50px quando o utilizador faz scroll além de um determinado ponto. Se a alteração de altura for animada com transition: height, substitua por transform: scaleY().

Barras de progresso associadas ao scroll que usam width. Um indicador de progresso de leitura que cresce da esquerda para a direita ao animar a 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 recorrer a JavaScript. Como estas animações usam transform e opacity por predefinição e correm no compositor, nunca causam layout shifts. A compatibilidade abrange o Chrome 115+ e o Edge 115+, com o Safari ainda em desenvolvimento. Para efeitos ligados ao scroll, como parallax, barras de progresso e animações de revelação, esta é a solução mais limpa. Se quer abandonar o scroll em JavaScript por completo, a API de scroll-driven animations é o caminho a seguir.

Porque é que o Lighthouse não deteta isto

O Lighthouse executa uma simulação do carregamento da página. Não faz scroll na página. Não interage com a página após o carregamento. Isto significa que os layout shifts acionados por scroll são completamente invisíveis em testes de laboratório.

A única forma de detetar estes shifts é com Real User Monitoring. O field data regista todo o ciclo de vida da página, incluindo cada layout shift que ocorre enquanto o utilizador faz scroll, clica e navega. Se a pontuação de CLS no CrUX for pior do que no Lighthouse, as animações acionadas por scroll são das primeiras coisas a investigar. O CoreDash monitoriza o CLS de visitantes reais, incluindo todos os shifts causados pelo scroll, para que veja exatamente que elementos são os responsáveis.

Como encontrar layout shifts provocados por scroll

Abra as DevTools do Chrome. Vá ao painel Performance. Inicie a gravação (o ícone circular) e faça scroll na página para cima e para baixo várias vezes. Pare a gravação. Procure os losangos roxos na faixa Layout Shifts. Se vir um aglomerado de shifts alinhado com a atividade de scroll, tem um problema de CLS provocado por scroll. Também pode usar a extensão de Chrome Core Web Vitals Visualizer para ver o CLS em tempo real enquanto faz scroll.

Regra prática

Se se move no scroll, anime com transform. Se faz fade no scroll, anime com opacity. Estas propriedades correm exclusivamente no compositor.

No JavaScript: verifique os event handlers de scroll, os callbacks de IntersectionObserver e qualquer código JavaScript que altere os estilos dos elementos em resposta à posição do scroll. Se recorrer a top, left, margin, width, height ou padding, substitua pelo transform equivalente.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

Arjen Karel

Precisa de um site mais rápido?

Sou o Arjen e há 17 anos que corrijo sites lentos. Se tem problemas que não consegue resolver sozinho, envie-me o seu URL e falamos.

Envie-me o seu URL
Arjen Karel
Arjen Karel, Consultor de web performance desde 2009

Porque é que o nosso fix não está a funcionar?

Por vezes a CDN ainda serve o HTML antigo, ou o fix foi feito no template de desktop e o mobile usa outro. Envie-me o seu URL e o problema, e respondo com alguns horários. Primeiro analiso os seus field data, depois dedicamos uma hora ao seu site, e no prazo de um dia tem as minhas notas por escrito. 350 € sem IVA.

Envie-me o seu URL

Sem programador para fazer o fix? Faço eu o trabalho, numa cópia do seu site.