Como corrigir o CLS: o que faz a página pular
O que conta no CLS, as causas comuns (imagens, anúncios, fontes, animações) e como reservar espaço em HTML e CSS. Como achar o elemento que se move.
Nesta página 7 min de leitura
O CLS (Cumulative Layout Shift) mede quanto o conteúdo visível muda de lugar sem que o usuário tenha pedido. É bom até 0,1 e ruim acima de 0,25, no percentil 75 das visitas (web.dev). As causas mais comuns são imagens, anúncios e iframes sem tamanho definido, conteúdo inserido depois do carregamento, troca de fonte e animações que mexem no layout. Quase todas se resolvem reservando o espaço do elemento antes de ele chegar.
Este texto segue os guias do web.dev sobre CLS e otimização de CLS (atualizado em fevereiro de 2025). Para o contexto das três Core Web Vitals, veja o guia de Core Web Vitals.
Como o CLS é calculado
Um deslocamento de layout acontece quando um elemento visível muda de posição entre dois quadros. Cada deslocamento recebe uma pontuação:
pontuação = fração de impacto × fração de distância
A fração de impacto é a área visível ocupada pelos elementos que se moveram (somando a posição antiga e a nova) dividida pela área da tela.
A fração de distância é o maior movimento de um desses elementos, na horizontal ou na vertical, dividido pela maior dimensão da tela.
Os deslocamentos são agrupados em janelas de sessão. Uma janela junta deslocamentos com menos de 1 segundo entre um e outro e dura no máximo 5 segundos. O CLS da página é a soma da janela com a maior pontuação, e não a soma de todos os deslocamentos da visita (web.dev). Por isso uma página aberta por muito tempo não acumula CLS sem limite.
Deslocamentos esperados não contam
Se o usuário clica num botão que abre um painel, o conteúdo se mover é o resultado esperado. A API marca com hadRecentInput os deslocamentos que acontecem até 500 ms depois de uma interação discreta (toque, clique ou tecla), e eles ficam fora do CLS. Rolagem, arrastar e gesto de pinça não abrem essa janela (web.dev). Se a resposta a um clique demora mais que 500 ms para mudar o layout, como num "carregar mais" que espera a rede, o deslocamento passa a contar. Nesses casos, reserve o espaço ou mostre um indicador de carregamento logo no clique.
Causas comuns e como corrigir
Imagens sem largura e altura
Sem dimensões, o navegador não sabe quanto espaço reservar e só descobre quando a imagem chega, empurrando o texto para baixo. A correção é declarar width e height no HTML. Os navegadores atuais usam esses dois valores para calcular a proporção e reservar o espaço, mesmo quando o CSS deixa a imagem responsiva (web.dev):
<img src="/img/produto.jpg" width="640" height="360" alt="Descrição do produto">img {
max-width: 100%;
height: auto;
}O navegador aplica internamente aspect-ratio: auto 640 / 360. A palavra auto faz a proporção real do arquivo valer depois que ele baixa. Os valores de width e height não precisam ser o tamanho exibido, só precisam ter a proporção certa.
Em imagens responsivas, todas as opções do srcset devem ter a mesma proporção. Se cada tamanho de tela usa um recorte diferente, declare as dimensões em cada <source> do <picture>:
<picture>
<source media="(max-width: 799px)" srcset="/img/banner-480-recorte.jpg"
width="480" height="400">
<source media="(min-width: 800px)" srcset="/img/banner-800.jpg"
width="800" height="400">
<img src="/img/banner-800.jpg" width="800" height="400" alt="Descrição do banner">
</picture>Para elementos que não têm atributos de dimensão, como um contêiner de vídeo, a propriedade CSS aspect-ratio reserva o espaço. Ela funciona no Chrome 88, no Firefox 89 e no Safari 15 em diante (MDN browser-compat-data).
.video-container {
aspect-ratio: 16 / 9;
width: 100%;
}Anúncios, embeds e iframes
Blocos de anúncio, vídeos incorporados e widgets de terceiros costumam chegar depois do conteúdo e com tamanho variável. O web.dev recomenda (web.dev):
reservar o espaço com
min-height, ou comaspect-ratioquando o conteúdo é responsivo;usar como base o tamanho do menor criativo esperado, e media queries quando o tamanho muda entre celular e desktop;
manter um placeholder quando nenhum anúncio é entregue, em vez de fechar o espaço, porque remover o espaço reservado também causa deslocamento.
/* Os valores são exemplo: use o tamanho do menor anúncio de cada posição */
.anuncio-topo {
min-height: 100px;
}
@media (min-width: 768px) {
.anuncio-topo {
min-height: 90px;
}
}Conteúdo inserido depois do carregamento
Avisos, faixas de promoção, formulários e listas que aparecem por JavaScript empurram o que está abaixo se não tiverem espaço reservado. As opções do web.dev:
reservar o espaço do elemento desde o HTML inicial;
posicionar o conteúdo tardio mais abaixo na página, onde o deslocamento afeta menos área visível;
em listas e feeds, trocar o conteúdo dentro de um contêiner de tamanho fixo, ou deixar o usuário carregar mais com um botão (o deslocamento logo após o clique não conta);
carregar o conteúdo novo fora da tela e avisar que há novidades, em vez de inseri-lo no topo.
Fontes web
Enquanto a fonte web baixa, o navegador mostra o texto invisível ou com uma fonte reserva. Quando a fonte web chega e tem medidas diferentes da reserva, as linhas mudam de largura e altura e o conteúdo se move. O comportamento é controlado por font-display (web.dev):
| Valor | Texto invisível por até | Troca pela fonte web | Efeito no CLS |
|---|---|---|---|
block | 2 a 3 s | a qualquer momento | pode deslocar na troca |
swap | 0 ms | a qualquer momento | pode deslocar na troca |
fallback | 100 ms | até 3 s | pode deslocar na troca |
optional | 100 ms | não troca depois | não desloca por troca de fonte |
Com optional, a fonte web só é usada se estiver pronta no primeiro layout. Se a marca exige a fonte web sempre, use swap e aproxime a fonte reserva das medidas dela. Isso se faz com uma regra @font-face para a fonte local de reserva, ajustada com size-adjust e com as propriedades de métrica ascent-override, descent-override e line-gap-override (Chrome for Developers):
@font-face {
font-family: "poppins-fallback";
src: local("Arial");
size-adjust: 60.85099821%;
ascent-override: 164.3358416%;
descent-override: 57.51754455%;
line-gap-override: 16.43358416%;
}
body {
font-family: "Poppins", "poppins-fallback", sans-serif;
}Os valores acima são os do artigo do Chrome para a Poppins sobre a Arial; cada par de fontes tem os seus, calculados a partir das métricas dos arquivos. Uma reserva diferente, como a Roboto no Android, precisa de outro conjunto. Sobre suporte, segundo os dados do MDN: size-adjust funciona no Chrome 92, no Firefox 92 e no Safari 17 em diante; as três propriedades de override existem no Chrome e no Firefox, mas no Safari aparecem só na versão de testes (Technology Preview) (MDN browser-compat-data, consultado em outubro de 2026). No Safari estável, portanto, só o size-adjust vale.
O web.dev também recomenda o preload das fontes críticas com <link rel="preload">, para que cheguem mais cedo. O preload disputa banda com outros recursos; use só para as fontes da primeira tela. A mesma escolha de font-display afeta o LCP quando o maior elemento é texto (veja como melhorar o LCP).
Animações que mexem no layout
Animar propriedades de posição e tamanho, como top, left, width e height, recalcula o layout e pode gerar deslocamentos. Use transform (translate, scale, rotate), que move o elemento sem mexer no fluxo da página (web.dev):
/* Evite */
.aviso { position: relative; transition: top 0.3s; }
.aviso.aberto { top: 0; }
/* Prefira */
.aviso { transition: transform 0.3s; transform: translateY(-100%); }
.aviso.aberto { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
.aviso { transition: none; }
}Como achar o elemento que se moveu
No Chrome DevTools, a visão de métricas ao vivo do painel Performance mostra o CLS local e uma lista de deslocamentos com o elemento, o momento e a pontuação. Numa gravação, a faixa Layout Shifts agrupa os deslocamentos e o insight "Layout shift culprits" aponta as causas prováveis (web.dev).
O Lighthouse lista imagens sem
widtheheighte os elementos que se deslocaram no carregamento.Para dados de usuários reais, a biblioteca web-vitals na versão de atribuição informa o elemento do maior deslocamento, quando ele ocorreu, quanto pontuou e em que fase do carregamento a página estava (GitHub GoogleChrome/web-vitals).
import {onCLS} from 'web-vitals/attribution';
onCLS(({value, attribution}) => {
console.log('CLS', value);
console.log('Elemento', attribution.largestShiftTarget);
console.log('Quando (ms)', attribution.largestShiftTime);
console.log('Pontuação', attribution.largestShiftValue);
console.log('Estado da página', attribution.loadState);
}, {reportAllChanges: true});A opção reportAllChanges ajuda a depurar porque avisa a cada aumento do CLS; o README da biblioteca não recomenda usá-la em produção. A API que mede os deslocamentos (Layout Instability) só existe nos navegadores Chromium: a web-vitals informa suporte de onCLS() apenas no Chromium (GitHub GoogleChrome/web-vitals). Os dados de campo de CLS, no RUM ou no CrUX, vêm desses navegadores. A diferença entre dado de laboratório e de campo está em Lighthouse, CrUX e RUM.
Cache de voltar e avançar (bfcache)
Quando o usuário volta para uma página que estava no bfcache, ela é restaurada da memória exatamente como estava, sem repetir os deslocamentos do carregamento, e o CLS dessa visita recomeça do zero (web.dev). Por isso vale deixar as páginas aptas a entrar nesse cache. Os impedimentos mais comuns são listeners de unload (troque por pagehide), o cabeçalho Cache-Control: no-store no HTML e conexões abertas (IndexedDB, WebSocket, requisições em andamento) no momento em que o usuário sai. No Chrome DevTools, em Application, a seção Back/forward cache tem um teste que mostra se a página foi restaurada e por que não.
Como verificar o seu site
No WebVyze, o diagnóstico de CLS usa o Google PageSpeed para mostrar a situação da página, e o teste de Core Web Vitals traz o CLS do CrUX dos últimos 28 dias, no celular e no desktop, para até 10 URLs. As duas ferramentas são grátis. Nos planos pagos, um script de visitantes reais baseado na biblioteca web-vitals registra o CLS de cada visita com a causa. Para problemas de resposta ao clique, veja como melhorar o INP.
Perguntas frequentes
- O CLS tem unidade, como segundos?
- Não. É um número sem unidade que combina quanto da área visível se moveu e a distância do movimento.
- Rolar a página conta como interação para excluir o deslocamento?
- Não. A exclusão de 500 ms vale para toques, cliques e teclas. Rolagem, arrastar e gesto de pinça não contam.
- Deslocamentos dentro de iframes entram no CLS?
- Entram no CLS do CrUX, porque o usuário vê esses movimentos. As APIs do navegador não conseguem medi-los, então a biblioteca web-vitals não os enxerga.
- Por que o CLS do Lighthouse é menor que o do CrUX?
- O Lighthouse costuma medir só o carregamento inicial. O CrUX mede a vida inteira da página, inclusive o que acontece depois da rolagem e das interações. Uma diferença grande sugere deslocamentos depois do carregamento.
Veja o que o seu site precisa corrigir.
Checagem grátis por lei, com a causa e o guia de correção de cada problema.
Continue lendo
- SEO técnico
Robôs de IA no robots.txt: busca, acesso do usuário e treinamento
GPTBot, ClaudeBot, OAI-SearchBot, Google-Extended: o que cada robô de IA faz, se respeita o robots.txt e como bloquear o treinamento sem sair da busca.
- SEO técnico
Redirecionamentos: cadeias, loops e como corrigir
Como o Google trata 301, 302, 307 e 308, o que cadeias e loops causam no rastreamento e na velocidade, e como mapear, corrigir e testar redirecionamentos.
- SEO técnico
llms.txt: o que é, o que não faz e como criar
O llms.txt resume o site para agentes de IA. Veja o formato, o que Google e outras empresas dizem sobre ele, quando vale criar e como escrever um bom arquivo.