Um tutorial sobre como usar o WebPageTest para identificar e corrigir problemas de instabilidade do layout.
Em uma postagem anterior, escrevi sobre como medir o Cumulative Layout Shift (CLS) no WebPageTest. A CLS é uma agregação de todas as mudanças de layout. Por isso, nesta postagem, achei que seria interessante analisar mais a fundo e inspecionar cada mudança de layout individual em uma página para tentar entender o que poderia estar causando a instabilidade e corrigir os problemas.
Como medir mudanças de layout
Usando a API Layout Instability, podemos ter uma lista de todos os eventos de mudança de layout em uma página:
new Promise(resolve => {
new PerformanceObserver(list => {
resolve(list.getEntries().filter(entry => !entry.hadRecentInput));
}).observe({type: "layout-shift", buffered: true});
}).then(console.log);
Isso produz uma matriz de deslocamentos de layout que não são precedidos por eventos de entrada:
[
{
"name": "",
"entryType": "layout-shift",
"startTime": 210.78500000294298,
"duration": 0,
"value": 0.0001045969445437389,
"hadRecentInput": false,
"lastInputTime": 0
}
]
Neste exemplo, houve uma única mudança muito pequena de 0,01% em 210 ms.
Saber o tempo e a gravidade da mudança é útil para ajudar a restringir o que pode ter causado a mudança. Vamos voltar ao WebPageTest para um ambiente de laboratório e fazer mais testes.
Como medir mudanças de layout no WebPageTest
Assim como a medição da CLS no WebPageTest, a medição de mudanças de layout individuais vai exigir uma métrica personalizada. Felizmente, o processo é mais fácil agora que o Chrome 77 está estável. A API Layout Instability é ativada por padrão. Assim, você pode executar esse snippet de JS em qualquer site no Chrome 77 e receber resultados imediatamente. No WebPageTest, você pode usar o navegador Chrome padrão e não precisa se preocupar com flags de linha de comando ou com o uso do Canary.
Vamos modificar esse script para produzir uma métrica personalizada para o WebPageTest:
[LayoutShifts]
return new Promise(resolve => {
new PerformanceObserver(list => {
resolve(JSON.stringify(list.getEntries().filter(entry => !entry.hadRecentInput)));
}).observe({type: "layout-shift", buffered: true});
});
A promessa neste script é resolvida como uma representação JSON da matriz, e não a própria matriz. Isso ocorre porque as métricas personalizadas só podem produzir tipos de dados primitivos, como strings ou números.
O site que vou usar para o teste é ismyhostfastyet.com, que criei para comparar o desempenho de carregamento real de hosts da Web.
Como identificar as causas de instabilidade do layout
Nos resultados, a métrica personalizada do LayoutShift tem este valor:
[
{
"name": "",
"entryType": "layout-shift",
"startTime": 3087.2349999990547,
"duration": 0,
"value": 0.3422101449275362,
"hadRecentInput": false,
"lastInputTime": 0
}
]
Em resumo, há uma única mudança de layout de 34,2% que ocorre em 3087 ms. Para ajudar a identificar o culpado, vamos usar a visualização de filme do WebPageTest.
Rolar até a marca de aproximadamente 3 segundos na tira de filme mostra exatamente a causa da mudança de layout de 34%: a tabela colorida. O site busca um arquivo JSON de forma assíncrona e o renderiza em uma tabela. A tabela está inicialmente vazia. Portanto, a espera para preenchê-la quando os resultados são carregados está causando a mudança.
Mas isso não é tudo. Quando a página fica visualmente completa em cerca de 4,3 segundos, podemos ver que o <h1>
da página "Meu host já está rápido?" aparece do nada. Isso acontece porque o site usa uma fonte da Web e não tomou nenhuma medida para otimizar a renderização. O layout não parece mudar quando isso acontece, mas ainda é uma experiência ruim para o usuário ter que esperar tanto para ler o título.
Como corrigir a instabilidade do layout
Agora que sabemos que a tabela gerada de forma assíncrona está causando a mudança de um terço da viewport, é hora de corrigir o problema. Não sabemos o conteúdo da tabela até que os resultados JSON sejam carregados, mas ainda podemos preencher a tabela com algum tipo de dados de marcador de posição para que o layout seja relativamente estável quando o DOM for renderizado.
Confira o código para gerar dados de marcador de posição:
function getRandomFiller(maxLength) {
var filler = '█';
var len = Math.ceil(Math.random() * maxLength);
return new Array(len).fill(filler).join('');
}
function getRandomDistribution() {
var fast = Math.random();
var avg = (1 - fast) * Math.random();
var slow = 1 - (fast + avg);
return [fast, avg, slow];
}
// Temporary placeholder data.
window.data = [];
for (var i = 0; i < 36; i++) {
var [fast, avg, slow] = getRandomDistribution();
window.data.push({
platform: getRandomFiller(10),
client: getRandomFiller(5),
n: getRandomFiller(1),
fast,
avg,
slow
});
}
updateResultsTable(sortResults(window.data, 'fast'));
Os dados do marcador de posição são gerados aleatoriamente antes de serem classificados. Ele inclui o caractere "█" repetido um número aleatório de vezes para criar marcadores visuais para o texto e uma distribuição gerada aleatoriamente dos três valores principais. Também adicionei alguns estilos para desaturar todas as cores da tabela e deixar claro que os dados ainda não foram totalmente carregados.
A aparência dos marcadores de posição usados não importa para a estabilidade do layout. O objetivo dos marcadores de posição é garantir aos usuários que o conteúdo está chegando e que a página não está corrompida.
Confira como os marcadores de posição ficam enquanto os dados JSON estão sendo carregados:
Resolver o problema da fonte da Web é muito mais simples. Como o site usa as fontes do Google, basta transmitir a propriedade display=swap
na solicitação do CSS. Isso é tudo. A API Fonts vai adicionar o estilo font-display: swap
na declaração de fonte, permitindo que o navegador renderize o texto em uma fonte de fallback imediatamente. Confira a marcação correspondente com a correção incluída:
<link href="https://fonts.googleapis.com/css?family=Chivo:900&display=swap" rel="stylesheet">
Como verificar as otimizações
Depois de executar a página novamente pelo WebPageTest, podemos gerar uma comparação antes e depois para visualizar a diferença e medir o novo grau de instabilidade do layout:
[
{
"name": "",
"entryType": "layout-shift",
"startTime": 3070.9349999997357,
"duration": 0,
"value": 0.000050272187989256116,
"hadRecentInput": false,
"lastInputTime": 0
}
]
De acordo com a métrica personalizada, ainda há uma mudança de layout ocorrendo em 3071 ms (aproximadamente o mesmo tempo anterior), mas a gravidade da mudança é muito menor: 0,005%. Posso viver com isso.
Também é claro na tira de filme que a fonte <h1>
volta imediatamente para uma fonte do sistema, permitindo que os usuários a leiam mais cedo.
Conclusão
Sites complexos provavelmente vão ter muitas mais mudanças de layout do que neste exemplo, mas o processo de correção é o mesmo: adicione métricas de instabilidade de layout ao WebPageTest, faça uma referência cruzada dos resultados com a tira de carregamento visual para identificar os culpados e implemente uma correção usando marcadores de posição para reservar o espaço da tela.
(One more thing) Como medir a instabilidade do layout experimentada por usuários reais
É bom poder executar o WebPageTest em uma página antes e depois de uma otimização e notar uma melhoria em uma métrica, mas o que realmente importa é que a experiência do usuário esteja melhorando. Não é por isso que estamos tentando melhorar o site?
Seria ótimo se começássemos a medir as experiências de instabilidade de layout de usuários reais com nossas métricas tradicionais de performance da Web. Esse é um elemento crucial do ciclo de feedback de otimização, porque ter dados do campo nos informa onde estão os problemas e se nossas correções fizeram diferença.
Além de coletar seus próprios dados de instabilidade de layout, confira o Relatório de UX do Chrome, que inclui dados de deslocamento cumulativo de layout de experiências reais de usuários em milhões de sites. Ele permite que você descubra como você (ou seus concorrentes) está se saindo ou pode ser usado para analisar o estado de instabilidade do layout na Web.