Cumulative Layout Shift (CLS)
Já aconteceu de você estar lendo um artigo online quando algo muda repentinamente na página? Sem aviso, o texto se move e você perdeu o seu lugar. Ou ainda pior: você está prestes a tocar num link ou botão, mas um instante antes do seu dedo pousar - BOOM - o link se move e você acaba clicando noutra coisa!
Na maioria das vezes, esse tipo de experiência é apenas irritante, mas, em alguns casos, pode causar danos reais.
O movimento inesperado do conteúdo da página geralmente ocorre porque os recursos são carregados de forma assíncrona ou os elementos do DOM são adicionados dinamicamente à página acima do conteúdo existente. O culpado pode ser uma imagem ou vídeo com dimensões desconhecidas, uma fonte que fica maior ou menor que sua substituta ou um anúncio ou widget de terceiros que se redimensiona dinamicamente.
O que torna esse problema ainda mais problemático é que o modo como um site funciona no desenvolvimento é geralmente muito diferente de como os usuários o experimentam. O conteúdo personalizado ou de terceiros geralmente não se comporta da mesma forma no desenvolvimento e na produção, as imagens de teste geralmente já estão no cache do navegador do desenvolvedor e as chamadas de API executadas localmente costumam ser tão rápidas que o atraso não é perceptível.
A métrica Cumulative Layout Shift (CLS) ajuda a resolver esse problema medindo a frequência com que ele ocorre para usuários reais.
O que é CLS? #
CLS é uma medida da maior explosão de ocorrências de mudança de layout para cada mudança de layout inesperada que ocorre durante toda a existência de uma página.
Uma mudança de layout ocorre sempre que um elemento visível muda sua posição de um quadro renderizado para o próximo. (Veja abaixo os detalhes sobre como as pontuações de mudança de layout individuais são calculadas.)
Uma explosão de mudanças de layout, conhecida como janela de sessão (session window), ocorre quando uma ou mais mudanças de layout individuais acontecem em rápida sucessão com menos de 1 segundo entre cada mudança e um máximo de 5 segundos para a duração total da janela.
A maior explosão é a janela da sessão com a pontuação cumulativa máxima de todas as mudanças de layout dentro dessa janela.
O que é uma boa pontuação CLS? #
Para fornecer uma boa experiência ao usuário, os sites devem se esforçar para ter uma pontuação CLS de 0,1 ou menos. Para garantir que você esteja atingindo essa meta para a maioria de seus usuários, um bom limite para medir é o 75º percentil de carregamentos de página, segmentado através de dispositivos móveis e desktop.
Mudanças de layout em detalhes #
As mudanças de layout são definidas pela API de instabilidade de layout, que relata entradas layout-shift
sempre que um elemento visível na viewport muda sua posição inicial (por exemplo, sua posição superior e esquerda no modo de escrita default) entre dois quadros. Esses elementos são considerados elementos instáveis.
Observe que as mudanças de layout ocorrem apenas quando os elementos existentes mudam sua posição inicial. Se um novo elemento for adicionado ao DOM ou um elemento existente mudar de tamanho, isto não conta como uma mudança de layout - desde que a mudança não faça com que outros elementos visíveis mudem sua posição inicial.
Pontuação de mudança de layout #
Para calcular a pontuação de mudança de layout , o navegador observa o tamanho do viewport e o movimento dos elementos instáveis no viewport entre dois quadros renderizados. A pontuação de mudança de layout é um produto de duas medidas desse movimento: a fração de impacto e a fração de distância (ambas definidas abaixo).
layout shift score = impact fraction * distance fraction
Fração de impacto #
A fração de impacto mede como os elementos instáveis afetam a área da janela de visualização (viewport) entre dois quadros.
A união das áreas visíveis de todos os elementos instáveis do quadro anterior e do quadro atual - como uma fração da área total da janela de visualização - é a fração de impacto do quadro atual.
Na imagem acima, há um elemento que ocupa metade da janela de visualização em um quadro. Depois, no quadro seguinte, o elemento é deslocado para baixo em 25% da altura da janela de visualização. O retângulo pontilhado vermelho indica a união da área visível do elemento em ambos os quadros, que, neste caso, é 75% da janela de visualização total, portanto, sua fração de impacto é de 0.75
.
Fração de distância #
A outra parte da equação de pontuação de mudança de layout mede a distância que os elementos instáveis se moveram em relação à janela de visualização. A fração de distância é a maior distância que qualquer elemento instável moveu no quadro (horizontal ou verticalmente) dividida pela maior dimensão da janela de visualização (largura ou altura, o que for maior).
No exemplo acima, a maior dimensão da janela de visualização é a altura, e o elemento instável foi movido em 25% da altura da janela de visualização, o que faz com que a fração de distância seja de 0,25.
Portanto, neste exemplo, a fração de impacto é 0.75
e a fração de distância é 0.25
, portanto, a pontuação de mudança de layout é 0.75 * 0.25 = 0.1875
.
O próximo exemplo mostra como a adição de conteúdo a um elemento existente afeta a pontuação de mudança de layout:
O botão "Click Me!" é anexado à parte inferior da caixa cinza com texto preto, o que empurra a caixa verde com texto branco para baixo (e parcialmente para fora da janela de visualização).
Neste exemplo, a caixa cinza muda de tamanho, mas sua posição inicial não muda, portanto não é um elemento instável.
O botão "Click Me!" não estava anteriormente no DOM, portanto, sua posição inicial também não muda.
A posição inicial da caixa verde, entretanto, muda, mas como ela foi movida parcialmente para fora da janela de visualização, a área invisível não é considerada ao calcular a fração de impacto. A união das áreas visíveis para a caixa verde em ambos os quadros (ilustrada pelo retângulo pontilhado vermelho) é a mesma que a área da caixa verde no primeiro quadro: 50% da janela de visualização. A fração de impacto é 0.5
.
A fração de distância é ilustrada com a seta roxa. A caixa verde foi movida para baixo em cerca de 14% da janela de visualização, então a fração de distância é 0.14
.
A pontuação de mudança de layout é 0.5 x 0.14 = 0.07
.
Este último exemplo ilustra vários elementos instáveis :
No primeiro quadro acima, há quatro resultados de uma solicitação de API para animais, classificados em ordem alfabética. No segundo quadro, mais resultados são adicionados à lista ordenada.
O primeiro item da lista ("Cat") não altera sua posição inicial entre os quadros, portanto, é estável. Da mesma forma, os novos itens adicionados à lista não estavam anteriormente no DOM, portanto, suas posições iniciais também não mudam. Mas os itens rotulados como "Dog", "Horse" e "Zebra" mudam suas posições iniciais, fazendo com que sejam elementos instáveis.
Novamente, os retângulos pontilhados vermelhos representam a união dessas três áreas instáveis antes e depois das áreas, que neste caso é cerca de 38% da área da janela de visualização (fração de 0.38
).
As setas representam as distâncias que os elementos instáveis se moveram desde suas posições iniciais. O elemento "Zebra", representado pela seta azul, foi o que mais se moveu, cerca de 30% da altura da janela de visualização. Isto faz com que a fração de distância neste exemplo seja 0.3
.
A pontuação de mudança de layout é 0.38 x 0.3 = 0.1172
.
Mudanças de layout esperadas vs. inesperadas #
Nem todas as mudanças de layout são ruins. Na verdade, muitos aplicativos dinâmicos da Web frequentemente mudam a posição inicial dos elementos na página.
Mudanças de layout iniciadas pelo usuário #
Uma mudança de layout só é ruim se o usuário não a espera. Por outro lado, as mudanças de layout que ocorrem em resposta às interações do usuário (clicar num link, pressionar um botão, digitar numa caixa de pesquisa e similares) geralmente são aceitáveis, contanto que a mudança ocorra perto o suficiente da interação que o relacionamento esteja claro para o usuário.
Por exemplo, se uma interação do usuário disparar uma solicitação de rede que pode demorar um pouco para ser concluída, é melhor imediatamente alocar algum espaço e mostrar um indicador de carregamento para evitar uma mudança desagradável de layout quando a solicitação for concluída. Se o usuário não perceber que algo está sendo carregado ou não tiver noção de quando o recurso estará pronto, ele pode tentar clicar em outra coisa enquanto espera, algo que pode sair do lugar na hora do clique.
As mudanças de layout que ocorrem dentro de 500 milissegundos da entrada do usuário terão o hadRecentInput
definido, para que possam ser excluídas dos cálculos.
Animações e transições #
Animações e transições, quando bem feitas, são uma ótima maneira de atualizar o conteúdo da página sem surpreender o usuário. O conteúdo que muda abrupta e inesperadamente na página quase sempre cria uma experiência ruim para o usuário. Mas o conteúdo que se move de forma gradual e natural de uma posição para a seguinte pode, muitas vezes, ajudar o usuário a entender melhor o que está acontecendo e guiá-lo entre as mudanças de estado.
A propriedade transform
do CSS permite animar elementos sem acionar mudanças de layout:
- Em vez de alterar as propriedades
height
ewidth
, usetransform: scale()
. - Para mover os elementos, evite alterar as propriedades
top
,right
,bottom
ouleft
e, em vez disso, usetransform: translate()
.
Como medir a CLS #
A CLS pode ser medida em laboratório ou em campo e está disponível nas seguintes ferramentas:
Ferramentas de campo #
- Relatório de Experiência do Usuário Chrome
- PageSpeed Insights
- Console de Busca (relatório Core Web Vitals)
- Biblioteca JavaScript
web-vitals
Ferramentas de laboratório #
Medição da CLS em JavaScript #
- Chrome 77, Supported 77
- Firefox, Not supported
- Edge 79, Supported 79
- Safari, Not supported
Para medir a CLS em JavaScript, você pode usar a API Layout Instability. O exemplo a seguir mostra como criar um PerformanceObserver
que escuta as entradas layout-shift
, as agrupa em sessões registra o valor máximo da sessão sempre que ela mudar.
let clsValue = 0;
let clsEntries = [];
let sessionValue = 0;
let sessionEntries = [];
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// Only count layout shifts without recent user input.
if (!entry.hadRecentInput) {
const firstSessionEntry = sessionEntries[0];
const lastSessionEntry = sessionEntries[sessionEntries.length - 1];
// If the entry occurred less than 1 second after the previous entry and
// less than 5 seconds after the first entry in the session, include the
// entry in the current session. Otherwise, start a new session.
if (sessionValue &&
entry.startTime - lastSessionEntry.startTime < 1000 &&
entry.startTime - firstSessionEntry.startTime < 5000) {
sessionValue += entry.value;
sessionEntries.push(entry);
} else {
sessionValue = entry.value;
sessionEntries = [entry];
}
// If the current session value is larger than the current CLS value,
// update CLS and the entries contributing to it.
if (sessionValue > clsValue) {
clsValue = sessionValue;
clsEntries = sessionEntries;
// Log the updated value (and its entries) to the console.
console.log('CLS:', clsValue, clsEntries)
}
}
}
}).observe({type: 'layout-shift', buffered: true});
Na maioria dos casos, o valor CLS no momento em que a página está sendo descarregada é o valor CLS final para essa página, mas existem algumas exceções importantes:
A seção a seguir lista as diferenças entre o que a API informa e como a métrica é calculada.
Diferenças entre a métrica e a API #
- Se uma página for carregada em segundo plano, ou se estiver em segundo plano antes do navegador renderizar qualquer conteúdo, ela não deve relatar nenhum valor CLS.
- Se uma página for restaurada do cache back/forward, seu valor CLS deve ser zerado, pois os usuários percebem isto como uma visita de página distinta.
- A API não relata entradas
layout-shift
para mudanças que ocorrem dentro de iframes, mas para medir corretamente a CLS, você deve considerá-las. Subquadros podem usar a API para relatar suas entradaslayout-shift
para o quadro pai para agregação.
Além dessas exceções, a CLS introduz uma complexidade adicional devido ao fato de que mede toda a vida útil de uma página:
- Os usuários podem manter uma aba aberta por muito tempo: dias, semanas, meses. Na verdade, um usuário pode nunca fechar uma aba.
- Em sistemas operacionais móveis, os navegadores normalmente não executam callbacks de descarregamento de página para abas em segundo plano, dificultando o relato do valor "final".
Para lidar com esses casos, a CLS deve ser relatada sempre que uma página entrar em segundo plano, além de quando for descarregada (o evento visibilitychange
cobre os dois cenários). E sistemas de análise que recebem esses dados precisarão calcular o valor da CLS final no back-end.
Em vez de memorizar e lidar com todos esses casos você mesmo, os desenvolvedores podem usar a biblioteca JavaScript web-vitals
para medir a CLS, que é responsável por tudo o que foi mencionado acima:
import {onCLS} from 'web-vitals';
// Measure and log CLS in all situations
// where it needs to be reported.
onCLS(console.log);
Para um exemplo completo de como medir a CLS em JavaScript, consulte o código-fonte de onCLS()
.
Como melhorar a CLS #
Para a maioria dos sites, você pode evitar todas as mudanças inesperadas de layout, aderindo a alguns princípios orientadores:
- Sempre inclua atributos de tamanho em suas imagens e elementos de vídeo ou reserve o espaço necessário usando algo como caixas de proporção de aspecto do CSS. Essa abordagem garante que o navegador possa alocar a quantidade correta de espaço no documento enquanto a imagem é carregada. Observe que você também pode usar a política de recursos de mídia não dimensionada para forçar esse comportamento em navegadores que oferecem suporte a políticas de recursos.
- Nunca insira conteúdo acima do conteúdo existente, exceto em resposta a uma interação do usuário. Isso garante que quaisquer mudanças de layout que ocorram sejam esperadas.
- Prefira animações de transformação a animações de propriedades que acionam mudanças de layout. Anime as transições de uma forma que forneça contexto e continuidade de um estado para outro.
Para obter informações detalhadas sobre como melhorar a CLS, veja Otimize a CLS e Depuração de mudanças de layout.
Recursos adicionais #
- Orientação do Google para tags sobre como minimizar a mudança de layout
- Understanding Cumulative Layout Shift (Compreendendo a mudança cumulativa de layout) por Annie Sullivan e Steve Kobes em #PerfMatters (2020)
CHANGELOG #
Occasionally, bugs are discovered in the APIs used to measure metrics, and sometimes in the definitions of the metrics themselves. As a result, changes must sometimes be made, and these changes can show up as improvements or regressions in your internal reports and dashboards.
To help you manage this, all changes to either the implementation or definition of these metrics will be surfaced in this CHANGELOG.
If you have feedback for these metrics, you can provide it in the web-vitals-feedback Google group.