Como corrigir problemas de deslocamento de layout cumulativo (CLS)
Publicados: 2022-03-10O deslocamento de layout cumulativo (CLS) tenta medir esses movimentos bruscos da página à medida que o novo conteúdo - sejam imagens, anúncios ou qualquer outra coisa - entra em jogo mais tarde do que o resto da página. Ele calcula uma pontuação com base em quanto da página está se movendo inesperadamente e com que frequência. Essas mudanças de conteúdo são muito irritantes, fazendo você perder seu lugar em um artigo que começou a ler ou, pior ainda, fazendo você clicar no botão errado!
Neste artigo, discutirei alguns padrões de front-end para reduzir o CLS . Não vou falar muito sobre como medir o CLS, pois já abordei isso em um artigo anterior. Nem vou falar muito sobre a mecânica de como o CLS é calculado: o Google tem uma boa documentação sobre isso, e o Guia Quase Completo para Mudança de Layout Cumulativo de Jess Peck também é um mergulho profundo e incrível nisso. No entanto, vou dar um pouco de fundo necessário para entender algumas das técnicas.
Por que o CLS é diferente
O CLS é, na minha opinião, o mais interessante dos Core Web Vitals, em parte porque é algo que nunca medimos ou otimizamos antes. Então, muitas vezes requer novas técnicas e formas de pensar para tentar otimizá-lo. É uma fera muito diferente dos outros dois Core Web Vitals.
Olhando brevemente para os outros dois Core Web Vitals, o Largest Contentful Paint (LCP) faz exatamente como o nome sugere e é mais uma reviravolta nas métricas de carregamento anteriores que medem a rapidez com que a página é carregada. Sim, alteramos a forma como definimos a experiência do usuário no carregamento da página para observar a velocidade de carregamento do conteúdo mais relevante , mas basicamente estamos reutilizando as técnicas antigas de garantir que o conteúdo seja carregado o mais rápido possível. Como otimizar seu LCP deve ser um problema relativamente bem compreendido para a maioria das páginas da web.
First Input Delay (FID) mede quaisquer atrasos nas interações e parece não ser um problema para a maioria dos sites. Otimizar isso geralmente é uma questão de limpar (ou reduzir!) seu JavaScript e geralmente é específico do site. Isso não quer dizer que resolver problemas com essas duas métricas seja fácil, mas são problemas razoavelmente bem compreendidos.
Uma razão pela qual o CLS é diferente é que ele é medido ao longo do tempo de vida da página — essa é a parte “cumulativa” do nome! Os outros dois Core Web Vitals param depois que o componente principal é encontrado na página após o carregamento (para LCP) ou para a primeira interação (para FID). Isso significa que nossas ferramentas tradicionais baseadas em laboratório, como o Lighthouse, geralmente não refletem totalmente o CLS, pois calculam apenas o CLS de carga inicial. Na vida real, um usuário rolará a página para baixo e poderá obter mais conteúdo, causando mais mudanças.
O CLS também é um número artificial que é calculado com base em quanto da página está se movendo e com que frequência. Enquanto o LCP e o FID são medidos em milissegundos, o CLS é um número sem unidade gerado por um cálculo complexo. Queremos que a página seja 0.1 ou inferior para passar neste Core Web Vital. Qualquer coisa acima de 0,25 é vista como “ruim”.
Mudanças causadas pela interação do usuário não são contadas . Isso é definido como dentro de 500 ms de um conjunto específico de interações do usuário, embora eventos de ponteiro e rolagem sejam excluídos. Presume-se que um usuário que clica em um botão pode esperar que o conteúdo apareça, por exemplo, expandindo uma seção recolhida.
O CLS trata da medição de mudanças inesperadas . A rolagem não deve fazer com que o conteúdo se mova se uma página for construída de maneira otimizada e, da mesma forma, passar o mouse sobre uma imagem do produto para obter uma versão ampliada, por exemplo, também não deve fazer com que o outro conteúdo salte. Mas é claro que existem exceções e esses sites precisam considerar como reagir a isso.
O CLS também está evoluindo continuamente com ajustes e correções de bugs. Ele acaba de ter uma mudança maior anunciada que deve dar algum alívio às páginas de longa duração, como aplicativos de página única (SPA) e páginas de rolagem infinita, que muitos sentiram que foram injustamente penalizadas no CLS. Em vez de acumular turnos ao longo de todo o tempo da página para calcular a pontuação do CLS, como foi feito até agora, a pontuação será calculada com base no maior conjunto de turnos dentro de uma janela de tempo específica.
Isso significa que, se você tiver três pedaços de CLS de 0,05, 0,06 e 0,04, anteriormente isso teria sido registrado como 0,15 (ou seja, acima do limite "bom" de 0,1), enquanto agora será pontuado como 0,06. Ainda é cumulativo no sentido de que a pontuação pode ser composta de mudanças separadas dentro desse período de tempo (ou seja, se essa pontuação CLS de 0,06 foi causada por três mudanças separadas de 0,02), mas não é mais cumulativa ao longo da vida útil total da página .
Dizendo isso, se você resolver as causas desse deslocamento de 0,06, seu CLS será relatado como o próximo maior (0,05), de modo que ainda está analisando todos os deslocamentos ao longo da vida útil da página - está apenas optando por relatar apenas o maior como a pontuação CLS.
Com essa breve introdução a algumas das metodologias sobre CLS, vamos passar para algumas das soluções ! Todas essas técnicas basicamente envolvem separar a quantidade correta de espaço antes que o conteúdo adicional seja carregado – seja mídia ou conteúdo injetado em JavaScript, mas há algumas opções diferentes disponíveis para os desenvolvedores da Web fazerem isso.
Definir largura e altura em imagens e iFrames
Já escrevi sobre isso antes, mas uma das coisas mais fáceis que você pode fazer para reduzir o CLS é garantir que você tenha os atributos de width
e height
definidos em suas imagens . Sem eles, uma imagem fará com que o conteúdo subsequente se desloque para abrir caminho após o download:
Isso é simplesmente uma questão de alterar sua marcação de imagem de:
<img src="hero_image.jpg" alt="...">
Para:
<img src="hero_image.jpg" alt="..." width="400" height="400">
Você pode encontrar as dimensões da imagem abrindo o DevTools e passando o mouse sobre (ou tocando) o elemento.
Aconselho usar o tamanho intrínseco (que é o tamanho real da fonte da imagem) e o navegador os reduzirá para o tamanho renderizado quando você usar CSS para alterá-los.
Dica rápida : Se, como eu, você não consegue se lembrar se é largura e altura ou altura e largura, pense nisso como coordenadas X e Y, então, como X, a largura é sempre fornecida primeiro.
Se você tiver imagens responsivas e usar CSS para alterar as dimensões da imagem (por exemplo, para restringi-la a uma max-width
de 100% do tamanho da tela), esses atributos podem ser usados para calcular a height
- desde que você lembre-se de substituir isso para auto
no seu CSS:
img { max-width: 100%; height: auto; }
Todos os navegadores modernos suportam isso agora, embora não o fizessem até recentemente, conforme abordado no meu artigo. Isso também funciona para elementos <picture>
e imagens srcset
(defina a width
e a height
no elemento img
de fallback), embora ainda não para imagens de diferentes proporções - está sendo trabalhado e, até então, você ainda deve definir width
e height
pois quaisquer valores serão melhores que os padrões de 0
por 0
!
Isso também funciona em imagens nativas de carregamento lento (embora o Safari ainda não suporte carregamento lento nativo por padrão).
A nova propriedade CSS aspect-ratio
A técnica de width
e height
acima, para calcular a altura de imagens responsivas, pode ser generalizada para outros elementos usando a nova propriedade CSS aspect-ratio
, que agora é suportada por navegadores baseados em Chromium e Firefox, mas também está no Safari Technology Preview. espero que isso signifique que ele chegará à versão estável em breve.
Então você pode usá-lo em um vídeo incorporado, por exemplo, na proporção 16:9:
video { max-width: 100%; height: auto; aspect-ratio: 16 / 9; }
<video controls width="1600" height="900" poster="..."> <source src="/media/video.webm" type="video/webm"> <source src="/media/video.mp4" type="video/mp4"> Sorry, your browser doesn't support embedded videos. </video>
Curiosamente, sem definir a propriedade aspect-ratio
, os navegadores ignorarão a altura dos elementos de vídeo responsivos e usarão uma proporção padrão de 2:1, portanto, o acima é necessário para evitar uma mudança de layout aqui.
No futuro, deve ser possível definir a aspect-ratio
dinamicamente com base nos atributos do elemento usando aspect-ratio: attr(width) / attr(height);
mas infelizmente isso ainda não é suportado.
Ou você pode até usar aspect-ratio
em um elemento <div>
para algum tipo de controle personalizado que você está criando para torná-lo responsivo:
#my-square-custom-control { max-width: 100%; height: auto; width: 500px; aspect-ratio: 1; }
<div></div>
Para os navegadores que não suportam aspect-ratio
você pode usar o hack de preenchimento inferior mais antigo, mas, com a simplicidade da aspect-ratio
mais recente e amplo suporte (especialmente quando isso muda do Safari Technical Preview para o Safari regular), é difícil justificar esse método mais antigo.
O Chrome é o único navegador que realimenta o CLS para o Google e suporta o significado aspect-ratio
que resolverá seus problemas de CLS em termos de Core Web Vitals. Eu não gosto de priorizar as métricas sobre os usuários, mas o fato de que os outros navegadores Chromium e Firefox têm isso e o Safari esperançosamente em breve, e que este é um aprimoramento progressivo significa que eu diria que estamos no ponto em que pode deixar o hack do fundo de preenchimento para trás e escrever um código mais limpo.
Faça uso liberal de min-height
Para aqueles elementos que não precisam de um tamanho responsivo, mas de uma altura fixa, considere usar min-height
. Isso pode ser para um cabeçalho de altura fixa , por exemplo, e podemos ter cabeçalhos diferentes para os diferentes pontos de interrupção usando consultas de mídia como de costume:
header { min-height: 50px; } @media (min-width: 600px) { header { min-height: 200px; } }
<header> ... </header>
É claro que o mesmo se aplica à min-width
para elementos colocados horizontalmente, mas normalmente é a altura que causa os problemas do CLS.
Uma técnica mais avançada para conteúdo injetado e seletores avançados de CSS é segmentar quando o conteúdo esperado ainda não foi inserido. Por exemplo, se você tivesse o seguinte conteúdo:
<div class="container"> <div class="main-content">...</div> </div>
E uma div
extra é inserida via JavaScript:
<div class="container"> <div class="additional-content">.../div> <div class="main-content">...</div> </div>
Em seguida, você pode usar o snippet a seguir para deixar espaço para conteúdo adicional quando o div main-content
for renderizado inicialmente.
.main-content:first-child { margin-top: 20px; }
Este código realmente criará um deslocamento para o elemento main-content
pois a margem conta como parte desse elemento, de modo que parecerá mudar quando for removido (mesmo que não se mova na tela). No entanto, pelo menos o conteúdo abaixo dele não será alterado, portanto, deve-se reduzir o CLS.
Alternativamente, você pode usar o pseudoelemento ::before
para adicionar o espaço para evitar o deslocamento no elemento main-content
também:
.main-content:first-child::before { content: ''; min-height: 20px; display: block; }
Mas com toda a honestidade, a melhor solução é ter o div
no HTML e usar min-height
.
Verificar elementos de fallback
Eu gosto de usar o aprimoramento progressivo para fornecer um site básico, mesmo sem JavaScript sempre que possível. Infelizmente, isso me surpreendeu recentemente em um site que mantenho quando a versão não JavaScript de fallback era diferente de quando o JavaScript entrou em ação.
O problema foi devido ao botão de menu "Índice" no cabeçalho. Antes que o JavaScript entre em ação, há um link simples, estilizado para se parecer com o botão que leva você à página do Índice. Uma vez que o JavaScript entra em ação, ele se torna um menu dinâmico para permitir que você navegue diretamente para qualquer página que você deseja ir a partir dessa página.
Eu usei elementos semânticos e, portanto, usei um elemento âncora ( <a href="#table-of-contents">
) para o link de fallback, mas o substituí por um <button>
para o menu dinâmico orientado a JavaScript. Eles foram estilizados para parecerem iguais, mas o link de fallback era alguns pixels menor que o botão!
Isso era tão pequeno, e o JavaScript geralmente entrava em ação tão rapidamente, que eu não tinha notado que estava desligado. No entanto, o Chrome percebeu isso ao calcular o CLS e, como estava no cabeçalho, deslocou a página inteira alguns pixels. Portanto, isso teve um grande impacto na pontuação do CLS – o suficiente para colocar todas as nossas páginas na categoria “Precisa de melhorias”.
Este foi um erro da minha parte, e a correção foi simplesmente sincronizar os dois elementos (também poderia ter sido corrigido definindo uma min-height
no cabeçalho, conforme discutido acima), mas isso me confundiu um pouco. Tenho certeza de que não sou o único a ter cometido esse erro, portanto, esteja ciente de como a página é renderizada sem JavaScript. Não acha que seus usuários desabilitam o JavaScript? Todos os seus usuários não são JS enquanto estão baixando seu JS.
Fontes da Web Causam Mudanças de Layout
As fontes da Web são outra causa comum de CLS devido ao navegador calcular inicialmente o espaço necessário com base na fonte de fallback e, em seguida, recalcular quando a fonte da Web for baixada. Normalmente, o CLS é pequeno, fornecendo uma fonte de fallback de tamanho semelhante, então, muitas vezes, eles não causam problemas suficientes para falhar no Core Web Vitals, mas podem ser chocantes para os usuários, no entanto.
Infelizmente, mesmo pré-carregar as webfonts não ajudará aqui, pois, embora isso reduza o tempo em que as fontes de fallback são usadas (portanto, é bom para carregar o desempenho - LCP), ainda leva tempo para buscá-las e, portanto, os fallbacks ainda serão usados pelo navegador na maioria dos casos, portanto, não evita o CLS. Dizendo isso, se você sabe que uma fonte da web é necessária na próxima página (digamos que você está em uma página de login e sabe que a próxima página usa uma fonte especial), você pode pré-buscá-las.
Para evitar completamente as mudanças de layout induzidas por fontes , é claro que não poderíamos usar fontes da Web - incluindo usar fontes do sistema ou usar font-display: optional
para não usá-las se não forem baixadas a tempo para a renderização inicial. Mas nenhum deles é muito satisfatório, para ser honesto.
Outra opção é garantir que as seções sejam dimensionadas adequadamente (por exemplo, com min-height
) para que, embora o texto nelas possa mudar um pouco, o conteúdo abaixo não será empurrado para baixo mesmo quando isso acontecer. Por exemplo, definir uma min-height
no elemento <h1>
pode impedir que todo o artigo se desloque para baixo se fontes um pouco mais altas forem carregadas — desde que as diferentes fontes não causem um número diferente de linhas. Isso reduzirá o impacto das mudanças, no entanto, para muitos casos de uso (por exemplo, parágrafos genéricos), será difícil generalizar uma altura mínima.
O que mais me empolga para resolver esse problema são os novos CSS Font Descriptors, que permitem ajustar mais facilmente as fontes de fallback em CSS:
@font-face { font-family: 'Lato'; src: url('/static/fonts/Lato.woff2') format('woff2'); font-weight: 400; } @font-face { font-family: "Lato-fallback"; size-adjust: 97.38%; ascent-override: 99%; src: local("Arial"); } h1 { font-family: Lato, Lato-fallback, sans-serif; }
Antes disso, era necessário ajustar a fonte de fallback usando a API de carregamento de fontes em JavaScript, que era mais complicada, mas essa opção que deve ser lançada muito em breve pode finalmente nos dar uma solução mais fácil com maior probabilidade de ganhar força. Veja meu artigo anterior sobre esse assunto para obter mais detalhes sobre essa inovação futura e mais recursos sobre isso.
Modelos iniciais para páginas renderizadas do lado do cliente
Muitas páginas renderizadas do lado do cliente, ou aplicativos de página única, renderizam uma página básica inicial usando apenas HTML e CSS e, em seguida, “hidratam” o modelo após o download e a execução do JavaScript.
É fácil para esses modelos iniciais ficarem fora de sincronia com a versão JavaScript, pois novos componentes e recursos são adicionados ao aplicativo no JavaScript, mas não adicionados ao modelo HTML inicial que é renderizado primeiro. Isso causa o CLS quando esses componentes são injetados pelo JavaScript.
Portanto, revise todos os seus modelos iniciais para garantir que eles ainda sejam bons marcadores iniciais. E se o modelo inicial consistir em <div>
s vazios, use as técnicas acima para garantir que eles sejam dimensionados adequadamente para tentar evitar quaisquer mudanças.
Além disso, o div
inicial que é injetado com o aplicativo deve ter um min-height
para evitar que seja renderizado com 0 height inicialmente antes que o modelo inicial seja inserido.
<div></div>
Contanto que min-height
seja maior do que a maioria das janelas de visualização , isso deve evitar qualquer CLS para o rodapé do site, por exemplo. O CLS é medido apenas quando está na janela de visualização e, portanto, afeta o usuário. Por padrão, um div
vazio tem uma altura de 0px, então dê a ele um min-height
mais próximo da altura real quando o aplicativo for carregado.
Garanta que as interações do usuário sejam concluídas em 500 ms
As interações do usuário que fazem com que o conteúdo mude são excluídas das pontuações do CLS. Estes são restritos a 500 ms após a interação. Portanto, se você clicar em um botão e fizer algum processamento complexo que leva mais de 500 ms e renderizar algum conteúdo novo, sua pontuação CLS sofrerá.
Você pode ver se o turno foi excluído no Chrome DevTools usando a guia Desempenho para gravar a página e, em seguida, encontrando os turnos, conforme mostrado na próxima captura de tela. Abra o DevTools vá para a aba Performance muito intimidante (mas muito útil quando você pegar o jeito!) e depois clique no botão de gravação no canto superior esquerdo (circulado na imagem abaixo) e interaja com sua página, e pare de gravar uma vez completo.
Você verá uma tira de filme da página na qual eu carreguei alguns dos comentários em outro artigo da Smashing Magazine, então na parte que eu circulei, você pode ver os comentários sendo carregados e o rodapé vermelho sendo deslocado para fora da tela. Mais abaixo na guia Desempenho , na linha Experiência , o Chrome colocará uma caixa rosa-avermelhada para cada turno e, quando você clicar nela, obterá mais detalhes na guia Resumo abaixo.
Aqui você pode ver que obtivemos uma pontuação enorme de 0,3359 - muito além do limite de 0,1 que pretendemos estar abaixo, mas a pontuação cumulativa não incluiu isso, porque a entrada recente está definida como Usos.
Garantir que as interações apenas desloquem o conteúdo dentro de 500 ms nas bordas do que o First Input Delay tenta medir, mas há casos em que o usuário pode ver que a entrada teve um efeito (por exemplo, um spinner de carregamento é mostrado), então o FID é bom, mas o conteúdo pode não ser adicionado à página até depois do limite de 500 ms, então o CLS é ruim.
Idealmente, toda a interação será concluída em 500 ms, mas você pode fazer algumas coisas para reservar o espaço necessário usando as técnicas acima enquanto o processamento estiver em andamento, de modo que, se levar mais do que os mágicos 500 ms, você terá já lidou com o turno e por isso não será penalizado por isso. Isso é especialmente útil ao buscar conteúdo da rede que pode ser variável e fora do seu controle.
Outros itens a serem observados são animações que levam mais de 500 ms e, portanto, podem afetar o CLS. Embora isso possa parecer um pouco restritivo, o objetivo do CLS não é limitar a “diversão”, mas definir expectativas razoáveis de experiência do usuário e não acho que seja irreal esperar que elas demorem 500 ms ou menos. Mas se você discordar ou tiver um caso de uso que eles podem não ter considerado, a equipe do Chrome está aberta a comentários sobre isso.
JavaScript síncrono
A técnica final que vou discutir é um pouco controversa, pois vai contra os conhecidos conselhos de desempenho da Web, mas pode ser o único método em determinadas situações. Basicamente, se você tem um conteúdo que sabe que causará mudanças, uma solução para evitar as mudanças é não renderizá-lo até que seja resolvido!
O HTML abaixo ocultará o div
inicialmente e, em seguida, carregará algum JavaScript de bloqueio de renderização para preencher o div
e, em seguida, reexibirá-lo. Como o JavaScript está bloqueando a renderização, nada abaixo disso será renderizado (incluindo o segundo bloco de style
para reexibi-lo) e, portanto, nenhuma mudança será incorrida.
<style> .cls-inducing-div { display: none; } </style> <div class="cls-inducing-div"></div> <script> ... </script> <style> .cls-inducing-div { display: block; } </style>
É importante inserir o CSS no HTML com essa técnica, para que seja aplicado na ordem. A alternativa é mostrar o conteúdo com o próprio JavaScript, mas o que eu gosto na técnica acima é que ela ainda mostra o conteúdo mesmo que o JavaScript falhe ou seja desligado pelo navegador.
Essa técnica também pode ser aplicada com JavaScript externo, mas isso causará mais atraso do que um script
embutido, pois o JavaScript externo é solicitado e baixado. Esse atraso pode ser minimizado pré-carregando o recurso JavaScript para que ele fique disponível mais rapidamente quando o analisador atingir essa seção de código:
<head> ... <link rel="preload" href="cls-inducing-javascript.js" as="script"> ... </head> <body> ... <style> .cls-inducing-div { display: none; } </style> <div class="cls-inducing-div"></div> <script src="cls-inducing-javascript.js"></script> <style> .cls-inducing-div { display: block; } </style> ... </body>
Agora, como eu disse, isso com certeza fará com que algumas pessoas de desempenho da Web se estremeçam, pois o conselho é usar async, defer
ou o mais novo type="module"
(que são defer
por padrão) em JavaScript especificamente para evitar o bloqueio render , enquanto estamos fazendo o oposto aqui! No entanto, se o conteúdo não pode ser pré-determinado e vai causar mudanças chocantes, então há pouco sentido em renderizá-lo cedo.
Usei essa técnica para um banner de cookie que carregava no topo da página e deslocava o conteúdo para baixo:
Isso exigia a leitura de um cookie para ver se o banner do cookie deveria ser exibido ou não e, embora isso pudesse ser concluído no lado do servidor, este era um site estático sem capacidade de alterar dinamicamente o HTML retornado.
Os banners de cookies podem ser implementados de diferentes maneiras para evitar o CLS. Por exemplo, colocando-os na parte inferior da página ou sobrepondo-os na parte superior do conteúdo, em vez de deslocar o conteúdo para baixo. Preferimos manter o conteúdo no topo da página, então tivemos que usar essa técnica para evitar os deslocamentos. Existem vários outros alertas e banners que os proprietários de sites podem preferir que estejam no topo da página por vários motivos.
Eu também usei essa técnica em outra página onde o JavaScript move o conteúdo para as colunas “principal” e “aparte” (por motivos que não vou detalhar, não foi possível construir isso corretamente no lado do servidor HTML). Mais uma vez, ocultar o conteúdo, até que o JavaScript tenha reorganizado o conteúdo, e só então mostrá-lo, evitou os problemas de CLS que estavam arrastando a pontuação CLS dessas páginas para baixo. E, novamente, o conteúdo é exibido automaticamente, mesmo que o JavaScript não seja executado por algum motivo e o conteúdo não alterado seja exibido.
O uso dessa técnica pode afetar outras métricas (particularmente LCP e também First Contentful Paint), pois você está atrasando a renderização e também bloqueando potencialmente o pré-carregador de visualização dos navegadores, mas é outra ferramenta a ser considerada para os casos em que não existe outra opção.
Conclusão
A mudança de layout cumulativa é causada por dimensões de alteração de conteúdo ou novo conteúdo sendo injetado na página por JavaScript de execução tardia. Neste post, discutimos várias dicas e truques para evitar isso. Fico feliz que os holofotes do Core Web Vitals tenham brilhado sobre esse problema irritante - por muito tempo nós desenvolvedores da web (e eu definitivamente me incluo nisso) ignoramos esse problema.
Limpar meus próprios sites levou a uma melhor experiência para todos os visitantes. Incentivo você a analisar seus problemas de CLS também , e esperamos que algumas dessas dicas sejam úteis quando você fizer isso. Quem sabe, você pode até conseguir chegar à pontuação indescritível de 0 CLS para todas as suas páginas!
Mais recursos
- Artigos do Core Web Vitals aqui na Smashing Magazine, incluindo o meu próprio sobre Setting Width and Heights on Images, Measuring Core Web Vitals e CSS Font Descriptors.
- Documentação do Core Web Vitals do Google, incluindo sua página no CLS.
- Mais detalhes sobre a mudança recente no CLS e então essa mudança começou a ser atualizada em várias ferramentas do Google.
- O CLS Changelog detalhando as alterações em cada versão do Chrome.
- O guia quase completo para mudança de layout cumulativo por Jess Peck.
- Mudança de layout cumulativa: meça e evite a instabilidade visual por Karolina Szczur.
- Um gerador de GIF de mudança de layout para ajudar a gerar demonstrações compartilháveis do CLS.