Saiba como encontrar interações lentas nos dados de campo do seu site para identificar oportunidades de melhorar a Interaction to Next Paint.
Os dados de campo mostram como os usuários reais estão interagindo com seu site. Ele revela problemas que não podem ser encontrados apenas nos dados de laboratório. No que diz respeito à Interaction to Next Paint (INP), os dados de campo são essenciais para identificar interações lentas e fornecem pistas vitais para ajudar você a corrigi-las.
Neste guia, você vai aprender a avaliar rapidamente a INP do seu site usando dados de campo do Chrome User Experience Report (CrUX) para conferir se o site tem problemas com a INP. Em seguida, você vai aprender a usar o build de atribuição da biblioteca JavaScript web-vitals e os novos insights que ele oferece com a API Long Animation Frames (LoAF) para coletar e interpretar dados de campo de interações lentas no seu site.
Comece com o CrUX para avaliar o INP do seu site
Se você não estiver coletando dados de campo dos usuários do seu site, o CrUX pode ser um bom ponto de partida. O CrUX coleta dados de campo de usuários reais do Chrome que optaram por enviar dados de telemetria.
Os dados do CrUX são mostrados em várias áreas diferentes, dependendo do escopo das informações que você está procurando. O CrUX pode fornecer dados sobre a INP e outras Core Web Vitals para:
- Páginas individuais e origens inteiras usando o PageSpeed Insights.
- Tipos de páginas. Por exemplo, muitos sites de e-commerce têm tipos de página de detalhes do produto e página de listagem de produtos. Você pode acessar os dados do CrUX para tipos de página exclusivos no Search Console.
Para começar, insira o URL do seu site no PageSpeed Insights. Depois de inserir o URL, os dados de campo dele, se disponíveis, serão mostrados para várias métricas, incluindo a INP. Você também pode usar os botões para verificar os valores de INP para as dimensões de dispositivos móveis e computadores.
Esses dados são úteis porque informam se há um problema. No entanto, o CrUX não pode informar o que está causando problemas. Há muitas soluções de monitoramento de usuários reais (RUM, na sigla em inglês) disponíveis que ajudam a coletar dados de campo dos usuários do seu site para responder a essa pergunta. Uma opção é coletar esses dados por conta própria usando a biblioteca JavaScript web-vitals.
Coletar dados de campo com a biblioteca JavaScript web-vitals
A biblioteca JavaScript web-vitals é um script que pode ser carregado no seu site para coletar dados de campo dos usuários. Você pode usá-la para registrar várias métricas, incluindo a INP em navegadores compatíveis.
A versão padrão da biblioteca web-vitals pode ser usada para receber dados básicos de INP de usuários em campo:
import {onINP} from 'web-vitals';
onINP(({name, value, rating}) => {
console.log(name); // 'INP'
console.log(value); // 512
console.log(rating); // 'poor'
});
Para analisar os dados de campo dos seus usuários, envie-os para algum lugar:
import {onINP} from 'web-vitals';
onINP(({name, value, rating}) => {
// Prepare JSON to be sent for collection. Note that
// you can add anything else you'd want to collect here:
const body = JSON.stringify({name, value, rating});
// Use `sendBeacon` to send data to an analytics endpoint.
// For Google Analytics, see https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics.
navigator.sendBeacon('/analytics', body);
});
No entanto, esses dados por si só não dizem muito mais do que o CrUX. É aí que entra o build de atribuição da biblioteca web-vitals.
Vá além com o build de atribuição da biblioteca web-vitals
A versão de atribuição da biblioteca web-vitals mostra dados adicionais que você pode receber dos usuários em campo para ajudar a resolver problemas de interações que estão afetando o INP do seu site. Esses dados podem ser acessados pelo objeto attribution exibido no método onINP() da biblioteca:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, rating, attribution}) => {
console.log(name); // 'INP'
console.log(value); // 56
console.log(rating); // 'good'
console.log(attribution); // Attribution data object
});
Além do INP da página, o build de atribuição fornece muitos dados que podem ser usados para entender os motivos das interações lentas, incluindo em qual parte da interação você deve se concentrar. Ele pode ajudar você a responder a perguntas importantes, como:
- "O usuário interagiu com a página enquanto ela estava carregando?"
- "Os manipuladores de eventos da interação foram executados por muito tempo?"
- "O código do manipulador de eventos de interação demorou para começar? Se sim, o que mais estava acontecendo na linha de execução principal naquele momento?"
- "A interação causou muito trabalho de renderização que atrasou a pintura do próximo frame?"
A tabela a seguir mostra alguns dos dados básicos de atribuição que você pode receber da biblioteca para ajudar a descobrir algumas causas de alto nível de interações lentas no seu site:
Chave de objeto attribution
|
Dados |
|---|---|
interactionTarget
|
Um seletor de CSS que aponta para o elemento que produziu o valor de INP da página, por exemplo, button#save.
|
interactionType
|
O tipo de interação, seja por cliques, toques ou entradas de teclado. |
inputDelay*
|
O atraso de entrada da interação. |
processingDuration*
|
O tempo desde o início da execução do primeiro listener de eventos em resposta à interação do usuário até o término de todo o processamento do listener de eventos. |
presentationDelay*
|
O atraso de apresentação da interação, que ocorre desde o momento em que os manipuladores de eventos terminam até o momento em que o próximo frame é renderizado. |
longAnimationFrameEntries*
|
Entradas do LoAF associadas à interação. Veja mais informações no próximo slide. |
A partir da versão 4 da biblioteca web-vitals, é possível ter insights ainda mais detalhados sobre interações problemáticas com os dados fornecidos com detalhamentos de fase da INP (atraso de entrada, duração do processamento e atraso de apresentação) e a API Long Animation Frames (LoAF).
A API Long Animation Frames (LoAF)
Depurar interações usando dados de campo é uma tarefa desafiadora. No entanto, com os dados da LoAF, agora é possível entender melhor as causas das interações lentas, já que ela expõe vários tempos detalhados e outros dados que você pode usar para identificar as causas exatas e, mais importante, onde está a origem do problema no código do site.
A versão de atribuição da biblioteca web-vitals expõe uma matriz de entradas de LoAF na chave longAnimationFrameEntries do objeto attribution. A tabela a seguir lista alguns dados importantes que você pode encontrar em cada entrada de LoAF:
| Chave do objeto de entrada do LoAF | Dados |
|---|---|
duration
|
A duração do frame longo de animação, até que o layout seja concluído, mas excluindo a pintura e a composição. |
blockingDuration
|
O tempo total no frame em que o navegador não conseguiu responder rapidamente devido a tarefas longas. Esse tempo de bloqueio pode incluir tarefas longas que executam JavaScript, bem como qualquer tarefa de renderização longa subsequente no frame. |
firstUIEventTimestamp
|
O carimbo de data/hora de quando o evento foi colocado na fila durante o frame. Útil para descobrir o início do atraso de entrada de uma interação. |
startTime
|
O carimbo de data/hora de início do frame. |
renderStart
|
Quando o trabalho de renderização do frame começou. Isso inclui todos os callbacks requestAnimationFrame (e ResizeObserver, se aplicável), mas possivelmente antes do início de qualquer trabalho de estilo/layout.
|
styleAndLayoutStart
|
Quando o trabalho de estilo/layout no frame ocorre. Pode ser útil para descobrir a duração do trabalho de estilo/layout ao considerar outros carimbos de data/hora disponíveis. |
scripts
|
Uma matriz de itens que contém informações de atribuição de script que contribuem para a INP da página. |
blockingDuration).
Todas essas informações podem dizer muito sobre o que torna uma interação lenta, mas a matriz scripts que as entradas do LoAF mostram é de interesse especial:
| Chave do objeto de atribuição de script | Dados |
|---|---|
invoker
|
O invocador. Isso pode variar de acordo com o tipo de invocador descrito na próxima linha. Exemplos de invocadores podem ser valores como 'IMG#id.onload', 'Window.requestAnimationFrame' ou 'Response.json.then'. |
invokerType
|
O tipo do invocador. Pode ser 'user-callback', 'event-listener', 'resolve-promise', 'reject-promise', 'classic-script' ou 'module-script'.
|
sourceURL
|
O URL do script de origem do frame longo de animação. |
sourceCharPosition
|
A posição do caractere no script identificado por sourceURL.
|
sourceFunctionName
|
O nome da função no script identificado. |
Cada entrada nessa matriz contém os dados mostrados nesta tabela, que fornece informações sobre o script responsável pela interação lenta e como ele foi responsável.
Medir e identificar causas comuns de interações lentas
Para dar uma ideia de como usar essas informações, este guia vai mostrar como usar os dados de LoAF mostrados na biblioteca web-vitals para determinar algumas causas de interações lentas.
Longas durações de processamento
A duração do processamento de uma interação é o tempo que leva para que os callbacks do manipulador de eventos registrados da interação sejam executados até a conclusão e qualquer outra coisa que possa acontecer entre eles. As durações de processamento altas são mostradas pela biblioteca web-vitals:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {processingDuration} = attribution; // 512.5
});
É natural pensar que a principal causa de uma interação lenta é que o código do manipulador de eventos levou muito tempo para ser executado, mas nem sempre é esse o caso. Depois de confirmar que esse é o problema, você pode se aprofundar com os dados do LoAF:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {processingDuration} = attribution; // 512.5
// Get the longest script from LoAF covering `processingDuration`:
const loaf = attribution.longAnimationFrameEntries.at(-1);
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
if (script) {
// Get attribution for the long-running event handler:
const {invokerType} = script; // 'event-listener'
const {invoker} = script; // 'BUTTON#update.onclick'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
Como você pode ver no snippet de código anterior, é possível trabalhar com dados de LoAF para rastrear a causa precisa de uma interação com valores altos de duração de processamento, incluindo:
- O elemento e o listener de eventos registrado.
- O arquivo de script e a posição do caractere nele que contém o código do manipulador de eventos de longa duração.
- O nome da função.
Esse tipo de dado é muito valioso. Não é mais necessário descobrir exatamente qual interação ou qual dos manipuladores de eventos dela foi responsável pelos altos valores de duração do processamento. Além disso, como os scripts de terceiros geralmente podem registrar os próprios manipuladores de eventos, é possível determinar se o código foi responsável ou não. Para o código que você controla, é recomendável otimizar tarefas longas.
Longos atrasos de entrada
Embora os manipuladores de eventos de longa duração sejam comuns, há outras partes da interação a serem consideradas. Uma parte ocorre antes da duração do processamento, que é conhecida como atraso de entrada. Esse é o tempo entre o início da interação pelo usuário e o momento em que os callbacks do manipulador de eventos começam a ser executados. Isso acontece quando a linha de execução principal já está processando outra tarefa. A versão de atribuição da biblioteca web-vitals pode informar a duração do atraso de entrada de uma interação:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
});
Se você notar que algumas interações têm atrasos de entrada altos, será necessário descobrir o que estava acontecendo na página no momento da interação que causou o atraso de entrada longo. Isso geralmente se resume a se a interação ocorreu enquanto a página estava carregando ou depois.
Foi durante o carregamento da página?
A linha de execução principal costuma ficar mais ocupada enquanto uma página é carregada. Durante esse período, todos os tipos de tarefas são colocados em fila e processados. Se o usuário tentar interagir com a página enquanto todo esse trabalho estiver acontecendo, isso poderá atrasar a interação. Páginas que carregam muito JavaScript podem iniciar o trabalho de compilar e avaliar scripts, além de executar funções que preparam uma página para interações do usuário. Esse trabalho pode atrapalhar se o usuário interagir enquanto essa atividade ocorre. Para descobrir se esse é o caso dos usuários do seu site:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
// Get the longest script from the first LoAF entry:
const loaf = attribution.longAnimationFrameEntries[0];
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
if (script) {
// Invoker types can describe if script eval blocked the main thread:
const {invokerType} = script; // 'classic-script' | 'module-script'
const {sourceLocation} = script; // 'https://example.com/app.js'
}
});
Se você registrar esses dados no campo e notar atrasos de entrada altos e tipos de invocador de 'classic-script' ou 'module-script', é provável que os scripts no seu site estejam demorando muito para serem avaliados e estejam bloqueando a linha de execução principal por tempo suficiente para atrasar as interações. Para reduzir esse tempo de bloqueio, divida os scripts em pacotes menores, adie o carregamento do código inicialmente não utilizado para um momento posterior e audite o site para remover o código não utilizado.
Foi depois do carregamento de página?
Embora os atrasos de entrada geralmente ocorram enquanto uma página está sendo carregada, é possível que eles ocorram depois que uma página é carregada, devido a uma causa totalmente diferente. As causas comuns de atrasos de entrada após o carregamento da página podem ser códigos que são executados periodicamente devido a uma chamada setInterval anterior ou até mesmo callbacks de eventos que foram colocados na fila para serem executados antes e ainda estão sendo processados.
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
// Get the longest script from the first LoAF entry:
const loaf = attribution.longAnimationFrameEntries[0];
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
if (script) {
const {invokerType} = script; // 'user-callback'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
Assim como na solução de problemas de valores altos de duração do processamento, atrasos de entrada altos devido às causas mencionadas anteriormente vão fornecer dados detalhados de atribuição de script. No entanto, o tipo de invocador muda de acordo com a natureza do trabalho que atrasou a interação:
'user-callback'indica que a tarefa de bloqueio era desetInterval,setTimeoutou até mesmorequestAnimationFrame.'event-listener'indica que a tarefa de bloqueio era de uma entrada anterior que foi colocada na fila e ainda está sendo processada.'resolve-promise'e'reject-promise'significa que a tarefa de bloqueio era de algum trabalho assíncrono que foi iniciado anteriormente e resolvido ou rejeitado no momento em que o usuário tentou interagir com a página, atrasando a interação.
De qualquer forma, os dados de atribuição do script vão dar uma ideia de onde começar a procurar e se o atraso de entrada foi causado pelo seu próprio código ou pelo de um script de terceiros.
Atrasos longos na apresentação
Os atrasos de apresentação são a última etapa de uma interação e começam quando os manipuladores de eventos da interação terminam, até o ponto em que o próximo frame é renderizado. Eles ocorrem quando o trabalho em um manipulador de eventos devido a uma interação muda o estado visual da interface do usuário. Assim como as durações de processamento e os atrasos de entrada, a biblioteca web-vitals pode informar quanto tempo durou o atraso de apresentação de uma interação:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 113.32307691
});
Se você registrar esses dados e notar atrasos de apresentação altos para interações que contribuem para o INP do seu site, os culpados podem variar, mas aqui estão algumas causas que você deve observar.
Trabalho de estilo e layout caro
Longos atrasos na apresentação podem ser causados por um trabalho caro de recalculação de estilo e layout que surge de várias causas, incluindo seletores de CSS complexos e tamanhos de DOM grandes. É possível medir a duração desse trabalho com os tempos de LoAF mostrados na biblioteca web-vitals:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 113.32307691
// Get the longest script from the last LoAF entry:
const loaf = attribution.longAnimationFrameEntries.at(-1);
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
// Get necessary timings:
const {startTime} = loaf; // 2120.5
const {duration} = loaf; // 1002
// Figure out the ending timestamp of the frame (approximate):
const endTime = startTime + duration; // 3122.5
// Get the start timestamp of the frame's style/layout work:
const {styleAndLayoutStart} = loaf; // 3011.17692309
// Calculate the total style/layout duration:
const styleLayoutDuration = endTime - styleAndLayoutStart; // 111.32307691
if (script) {
// Get attribution for the event handler that triggered
// the long-running style and layout operation:
const {invokerType} = script; // 'event-listener'
const {invoker} = script; // 'BUTTON#update.onclick'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
A LoAF não informa a duração do trabalho de estilo e layout de um frame, mas informa quando ele começou. Com esse carimbo de data/hora inicial, é possível usar outros dados do LoAF para calcular uma duração precisa desse trabalho, determinando o horário de término do frame e subtraindo o carimbo de data/hora inicial do trabalho de estilo e layout.
Callbacks de longa duração do requestAnimationFrame
Uma possível causa de atrasos longos na apresentação é o excesso de trabalho feito em um callback requestAnimationFrame. O conteúdo desse callback é executado depois que os manipuladores de eventos terminam de ser executados, mas antes do recálculo de estilo e do trabalho de layout.
Esses callbacks podem levar um tempo considerável para serem concluídos se o trabalho realizado neles for complexo. Se você suspeitar que os valores altos de atraso de apresentação se devem ao trabalho que está fazendo com requestAnimationFrame, use os dados do LoAF mostrados pela biblioteca web-vitals para identificar esses cenários:
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 543.1999999880791
// Get the longest script from the last LoAF entry:
const loaf = attribution.longAnimationFrameEntries.at(-1);
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
// Get the render start time and when style and layout began:
const {renderStart} = loaf; // 2489
const {styleAndLayoutStart} = loaf; // 2989.5999999940395
// Calculate the `requestAnimationFrame` callback's duration:
const rafDuration = styleAndLayoutStart - renderStart; // 500.59999999403954
if (script) {
// Get attribution for the event handler that triggered
// the long-running requestAnimationFrame callback:
const {invokerType} = script; // 'user-callback'
const {invoker} = script; // 'FrameRequestCallback'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
Se você notar que uma parte significativa do tempo de atraso de apresentação é gasta em um callback requestAnimationFrame, verifique se o trabalho que você está fazendo nesses callbacks se limita a realizar um trabalho que resulta em uma atualização real da interface do usuário. Qualquer outro trabalho que não toque no DOM ou atualize estilos vai atrasar desnecessariamente a pintura do próximo frame. Portanto, tenha cuidado.
Conclusão
Os dados de campo são a melhor fonte de informações para entender quais interações são problemáticas para os usuários reais em campo. Ao usar ferramentas de coleta de dados de campo, como a biblioteca JavaScript web-vitals (ou um provedor de RUM), você pode ter mais certeza sobre quais interações são mais problemáticas e, em seguida, reproduzir as interações problemáticas no laboratório para corrigi-las.