Corrigir o Layout shift culprits no Lighthouse e PageSpeed
O que se moveu enquanto a página carregava, como verificar o elemento que o Lighthouse culpa e como corrigir mudanças causadas por imagens, web fonts, anúncios, barras e CSS tardio.
Porque é que o Layout shift culprits está vermelho no PageSpeed Insights?
O Layout shift culprits fica vermelho quando as mudanças de layout durante o carregamento somam um CLS de 0,1 ou mais: "As mudanças de layout ocorrem quando os elementos se movem sem qualquer interação do utilizador." O Lighthouse chamava-lhe Avoid large layout shifts. Lista o que se moveu e tenta adivinhar a causa. Reserve espaço para o que chega tarde: largura e altura nas imagens, altura nos espaços de anúncios.
Quer apenas a solução rápida? Salte para imagens, web fonts, anúncios e embeds ou banners.
Como ler a tabela do Layout shift culprits?
O Lighthouse regista um carregamento da página num ecrã do tamanho de um telemóvel e lista as mudanças de layout em rajadas (mudanças que ocorrem com menos de um segundo de intervalo entre si), uma tabela por rajada. O aviso fica vermelho quando a pior rajada pontua 0,1 ou mais, e cinzento abaixo disso. Procure obter 0 mesmo quando estiver cinzento: uma mudança inferior a 0,1 continua a mover o texto que o visitante está a ler.
Numa página de receita que criei ao estilo de um blogue de culinária em WordPress, com o título numa web font, um plugin de newsletter, um anúncio sob o cabeçalho e um plugin de cartão de receita, o aviso ficou vermelho com uma rajada de 0,241.

O aviso da página de receita no Lighthouse 13.5, telemóvel, throttling simulado como no PageSpeed Insights.
- Total é a pontuação da rajada; o Total da pior rajada é o CLS reportado pelo Lighthouse.
- Element é o que mais se moveu nessa mudança, muitas vezes um contentor grande como
<main>. O que o empurrou situa-se normalmente acima. - Layout shift score é a parcela dessa mudança no Total.
- A linha sob cada entrada é a estimativa da causa feita pelo Lighthouse: "Unsized image element" com a imagem, ou "Web font" com o ficheiro do tipo de letra. Uma entrada pode ter ambos ou nenhum.
Na página de receita, o Lighthouse indicou a imagem errada em três das quatro mudanças: verifique que elemento se moveu realmente antes de alterar qualquer coisa.
Porque é que a página salta durante o carregamento?
O visitante começa a ler e o texto desliza, ou toca num link e acerta no que se moveu para o seu lugar.
O browser renderiza a página com o que tem. O que chega mais tarde sem espaço à espera empurra o conteúdo abaixo: uma imagem sem dimensões começa com altura zero, uma web font torna o título mais largo do que o fallback tornava. Cada movimento é uma mudança de layout, pontuada pela fração de ecrã que se moveu e pela distância. Mudanças com menos de um segundo de intervalo formam uma rajada, e o CLS é a pontuação da pior rajada; assim, pequenas mudanças consecutivas acumulam-se.
Na página de receita, cada causa fica abaixo de 0,1 isoladamente, e as quatro ocorrem numa única rajada:
| Causa na página de receita | CLS isolado | Após a correção |
|---|---|---|
| Web font do título | 0,035 | 0 |
| Foto da receita sem width e height | 0,036 | 0 |
| Barra de newsletter acima do cabeçalho | 0,058 | 0 |
| Anúncio num espaço vazio | 0,054 | 0 |
| Todas as quatro juntas | 0,241 | 0 |
Todas as quatro juntas pontuam mais do que a soma das linhas, porque na página completa o tipo de letra e a foto movem, cada um, uma área maior do ecrã. A correção das quatro fez a pontuação de Performance em telemóvel subir de 87 para 100, com FCP, LCP e Total Blocking Time inalterados. O CLS vale um quarto dessa pontuação, mas a Google posiciona a página com base no CLS que os visitantes reais obtêm.

Antes, first paint

Antes, aos 2 segundos

Depois, first paint

Depois, aos 2 segundos
A página de receita no tamanho de telemóvel do Lighthouse. A caixa tracejada marca onde a foto estava no first paint.
O Lighthouse culpou o elemento certo?
O Lighthouse deduz a causa pelo timing: uma web font que terminou o download imediatamente antes da mudança, uma imagem que o browser desenhou logo a seguir. Assim, uma imagem que foi apenas empurrada pode surgir listada como "Unsized image element". A imagem de destaque da página de receita tem width="1200" height="675", mas o Lighthouse indica-a sob as mudanças da fonte, da barra de newsletter e do anúncio. Para uma linha sem indicação de causa por baixo, comece no passo 2.
- A imagem indicada tem dimensões? "Image elements do not have explicit width and height", sob Diagnostics no mesmo relatório, lista as imagens sem dimensões. Nas DevTools, o separador Computed da imagem mostra uma proporção em aspect-ratio (auto 1200 / 675) quando o espaço está reservado, e apenas auto quando não está. Uma imagem com dimensões não causou a mudança, a menos que o tema defina width: auto (Unsized image element abaixo): verifique a outra linha sob a entrada, muitas vezes "Web font", ou avance para o passo 2.
- O que surgiu nesse momento? Grave um recarregamento no painel Performance. Cada mudança é um losango roxo na faixa Layout shifts; passe o cursor por cima para ver a pontuação e uma captura de ecrã desse momento com a área deslocada assinalada. Na página de receita, a mudança de 0,0583 mostra a barra verde da newsletter que tinha acabado de surgir no topo. Para mudanças durante o scroll, clique em Record, faça scroll e clique em Stop.
- Bloqueie o elemento suspeito e recarregue. No painel Network, clique com o botão direito no pedido suspeito, como o ficheiro do tipo de letra ou a tag de anúncio, escolha Block requests, depois Block request URL e grave novamente. Um losango que desapareça pertencia a esse pedido: bloquear o tipo de letra da página de receita removeu uma mudança e manteve as outras três. Desmarque o bloqueio na gaveta Request conditions no final.

Passo 2 na página de receita: Chrome 155, Slow 4G e abrandamento de CPU de 4×.
Sem as DevTools, o cRUM no CoreDash faz esta verificação: coloca um trace do Chrome da página ao lado do que os visitantes reais obtiveram, indica o elemento que se moveu e mostra o que o visitante fez imediatamente antes. A captura de ecrã abaixo é de uma página diferente: comece em Chrome trace e Real users.

CoreDash cRUM para uma página em acme-pagespeed.com, mobile.
Que elementos se movem para os visitantes reais?
O Lighthouse carrega a página uma única vez, como um visitante que entra pela primeira vez, e nunca faz scroll nem toca no ecrã. Os seus field data contêm todas as visitas reais, incluindo o anúncio a meio de um artigo que carrega quando o visitante faz scroll até ele e o cabeçalho que encolhe com o scroll. Quando o Lighthouse está limpo ou cinzento e o Search Console indica que o CLS é fraco, as mudanças acontecem normalmente após o carregamento.
No CoreDash, defina o dispositivo para mobile e agrupe o CLS por Attribution Element (CLS) para ver que elementos se moveram; Last interaction (CLS), sob More dimensions, indica se isso aconteceu após um scroll. Para encontrar o que empurrou esses elementos, grave essa página no painel Performance enquanto faz scroll (passo 2 da verificação acima).

CoreDash em mobile, p75: comece pelo CLS mais elevado.
Como resolver o Layout shift culprits?
Comece pela maior mudança na tabela com o Total mais elevado, assim que a verificação acima tiver confirmado a respetiva causa.
Unsized image element
Uma imagem sem dimensões começa com altura zero e empurra tudo o que estiver abaixo quando carrega. O WordPress adiciona width e height às imagens da biblioteca multimédia; as imagens geradas pelo próprio tema, page builder, slider ou plugin muitas vezes não os têm, como a foto do cartão de receita. Se não puder alterar o tema, o WP Rocket adiciona as dimensões em falta (Media, Add missing image dimensions); num template, escreva-as diretamente:
<img src="shakshuka.jpg" width="800" height="600" alt="Shakshuka in the pan">
img { max-width: 100%; height: auto; } O browser converte os dois atributos numa proporção e reserva a altura correta em qualquer largura de ecrã antes de o ficheiro chegar. Um width: auto no CSS do tema anula essa proporção (auto sizing de imagens). No Shopify, o filtro image_tag adiciona as dimensões; um tema ou app que escreva a sua própria tag <img> precisa de width="{{ image.width }}" height="{{ image.height }}". No Next.js, o next/image precisa de width e height, ou de fill dentro de um elemento pai com dimensões próprias.
Web font
O título surge primeiro no tipo de letra de fallback e depois na web font, que é mais larga ou mais estreita: as linhas quebram em palavras diferentes e tudo o que está abaixo move-se. No WordPress (Additional CSS) e no Shopify (na folha de estilos do tema sob Edit code; uma atualização do tema pode anular isso), adicione uma font face de fallback que redimensiona um tipo de letra local para as medidas da web font. No Next.js, o next/font adiciona um fallback correspondente. A fonte do título da página de receita:
@font-face {
font-family: "Young Serif Fallback";
src: local("Times New Roman"), local("Liberation Serif");
size-adjust: 131%;
ascent-override: 79.85%;
descent-override: 27.94%;
line-gap-override: 0%;
}
h1 { font-family: "Young Serif", "Young Serif Fallback", serif; } Um size-adjust de 122% obtido numa calculadora ainda deixava ali um CLS de 0,029; 131% resultou em 0. O artigo Font display inclui a regra para Android, como obter os seus próprios valores e o font-display: optional, que elimina a mudança e apresenta o fallback a quem visita pela primeira vez.
Anúncios, vídeos e outros embeds
O script de anúncios coloca o respetivo iframe no espaço após o leilão; se o espaço estava vazio, tudo o que está abaixo desce a altura do anúncio. De acordo com o State of Web Vitals, 89% dos sites sem redes de anúncios passam no CLS em telemóveis, contra 73% dos sites com duas ou mais redes. Isto é uma correlação.
No tema ou no plugin de anúncios, atribua ao contentor do anúncio a altura do formato mais frequente e defina o iframe como bloco:
.ad-slot { min-height: 50px; }
.ad-slot iframe { display: block; } Por defeito, um iframe alinha-se pela linha de texto, pelo que o espaço reservado na página de receita ainda crescia 8 píxeis até o iframe passar a bloco. Mantenha o espaço aberto quando não houver anúncio: fechá-lo move a página tanto quanto preenchê-lo. Os Auto ads do AdSense surgem onde a Google decide: exclua as áreas no primeiro ecrã (Ad settings, Excluded areas) e coloque aí uma unidade de anúncio manual num espaço com dimensões definidas.
O YouTube, mapas e outros embeds também são iframes, e num iframe os atributos width e height não definem uma proporção: com height: auto, ele encolhe para cerca de 150 píxeis. Defina um aspect-ratio no iframe ou use uma facade — uma miniatura com dimensões que se converte no iframe com um clique (YouTube, Google Maps):
.video-embed iframe { width: 100%; height: auto; aspect-ratio: 16 / 9; } Barras e banners inseridos por scripts
Uma barra de newsletter ou uma faixa de portes grátis que um script coloca acima do cabeçalho empurra a página inteira para baixo.
Comece pela opção de layout do plugin ou da app: uma barra inferior, uma caixa ou um popup flutuam sobre a página e não movem nada. Ferramentas de consentimento como a OneTrust e a CookieYes oferecem estes mesmos formatos; o Lighthouse vê o banner delas em cada execução porque arranca sem cookies. Uma barra que tenha obrigatoriamente de ficar acima do cabeçalho deve ter o seu espaço reservado no tema, com a altura que o separador Computed indica na largura de telemóvel:
<div id="promo-slot" style="min-height: 48px"></div> Para uma barra que só aparece em algumas visitas, deixe-a flutuar ou renderize-a no HTML no servidor. Conteúdo visível gerado por uma tag do Google Tag Manager chega sempre após o first paint: mova-o para o template. No Shopify, uma app que injete um widget, como estrelas de avaliação ou uma barra de avisos, precisa de uma altura definida no contentor que recebe o bloco da app.
CSS que carrega após o first paint
Uma folha de estilos carregada com o truque do media="print", ou adiada ou carregada de forma assíncrona por um plugin de otimização, chega depois de a página estar no ecrã e redimensiona o que já lá está: um cabeçalho que cresce empurra tudo o que estiver abaixo. No plugin, exclua-a da opção que adia o CSS ou que o carrega de forma assíncrona; no tema, carregue-a com um <link rel="stylesheet"> simples no head.
Animações, sliders e blocos renderizados por JavaScript
Estes vêm normalmente do tema, de um plugin ou de uma app: procure aí uma definição. Se não houver nenhuma, envie a um programador o elemento indicado no relatório e o código abaixo.
Uma animação de top, left, height ou margin altera o layout a cada frame. O Lighthouse lista-a como várias linhas curtas sem indicação de causa, e a auditoria "Avoid non-composited animations" sob Diagnostics indica o elemento. Em alternativa, anime a propriedade transform: move apenas os píxeis do elemento e nada ao redor, pelo que o browser não contabiliza qualquer mudança de layout.
.promo { position: fixed; top: 0; left: 0; right: 0;
transform: translateY(-100%); transition: transform .3s; }
.promo.is-open { transform: translateY(0); } Um slider mostra os respetivos slides empilhados até o seu script correr; mantenha-o no primeiro slide até o script adicionar a classe de estado pronto (use a do seu próprio slider):
.slider:not(.is-ready) > :not(:first-child) { display: none; } Um bloco que o JavaScript renderiza após o carregamento, como avaliações ou produtos relacionados, precisa de um contentor vazio na sua altura final ou de ser renderizado no servidor. Para animações no scroll, consulte scroll-triggered animations e CSS transitions.
Mudanças causadas por código que não pode editar
Não é possível alterar o que um criativo de anúncio, uma app sem opção de layout ou o script de um fornecedor inserem. Reserve a altura que normalmente ocupa, posicione-o abaixo do primeiro ecrã ou substitua-o por outro que respeite o seu espaço.
As alterações reduziram o CLS a 0?
Valide a correção no Lighthouse em telemóvel, com o plugin de cache e a CDN limpos. Sem qualquer mudança, o Layout shift culprits surge como aprovado e o CLS fica a 0. Uma gravação no painel Performance, feita uma vez com recarregamento e outra durante o scroll, apresenta a faixa Layout shifts vazia. O CoreDash mostra o novo CLS por elemento a partir das visitas seguintes; o Search Console demora até 28 dias.

Antes

Depois
Antes e depois das quatro correções: Lighthouse 13.5, telemóvel, throttling simulado.
Algumas mudanças surgem em sítios a que não tem acesso: um espaço de anúncio preenchido pelo parceiro publicitário, uma app que injeta o seu widget, uma mudança que só os visitantes reais veem após fazerem scroll. Numa Core Web Vitals power hour, analisamo-las nos seus field data e numa gravação da sua página, e decidimos que altura atribuir a cada contentor.

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
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 URLSem programador para fazer o fix? Faço eu o trabalho, numa cópia do seu site.