Encontrar interações lentas no campo

Aprenda a encontrar interações lentas nos dados de campo do seu site para encontrar oportunidades de melhorar a métrica "Interação com a próxima exibição".

Os dados de campo informam como os usuários reais estão usando seu site. Ele identifica problemas que não podem ser encontrados nos dados do laboratório. Quando a Interaction to Next Paint (INP) está relacionada, 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 o INP do seu site usando dados de campo do Chrome User Experience Report (CrUX) para saber se ele tem problemas com o INP. Em seguida, você vai aprender a usar o build de atribuição da biblioteca JavaScript da web-vitals e os novos insights que ela oferece da API Long Animation Frames (LoAF, na sigla em inglês) para coletar e interpretar dados de campo para 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 ativaram o envio de dados de telemetria.

Os dados do CrUX são mostrados em várias áreas diferentes e dependem 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 PageSpeed Insights.
  • Tipos de páginas. Por exemplo, muitos sites de e-commerce têm páginas de detalhes e de listagem de produtos. Você pode ver dados do CrUX para tipos de página únicos no Search Console.

Como ponto de partida, insira o URL do seu site no PageSpeed Insights. Depois que você 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 controles para conferir os valores de INP das dimensões para dispositivos móveis e computadores.

Dados de campo mostrados pelo CrUX no PageSpeed Insights, que mostram LCP, INP, CLS nas três Core Web Vitals, TTFB, FCP como métricas de diagnóstico e FID como uma métrica de Core Web Vitals descontinuada.
A leitura dos dados do CrUX, conforme mostrado nos insights do PageSpeed. Neste exemplo, o INP da página da Web precisa de melhorias.

Esses dados são úteis porque informam se você tem um problema. No entanto, o CrUX não pode dizer o que está causando problemas. Existem muitas soluções de monitoramento de usuário real (RUM, na sigla em inglês) disponíveis que ajudarão você a coletar seus próprios dados de campo dos usuários de seu site para ajudar a responder a isso, e uma opção é coletar esses dados de campo você mesmo usando a biblioteca JavaScript da 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á-lo para registrar várias métricas, incluindo a INP em navegadores compatíveis.

Compatibilidade com navegadores

  • Chrome: 96.
  • Edge: 96.
  • Firefox: não é compatível.
  • Safari: incompatível.

Origem

A versão padrão da biblioteca web-vitals pode ser usada para obter dados de INP básicos de usuários no 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 usuários, você precisa enviar esses dados 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 diria. É aí que entra o build de atribuição da biblioteca de Web Vitals.

Vá além com a build de atribuição da biblioteca web-vitals

O build de atribuição da biblioteca Web-vitals mostra dados adicionais que você pode coletar dos usuários no campo para ajudar a resolver melhor as interações problemáticas que estão afetando o INP do seu site. Esses dados podem ser acessados pelo objeto attribution mostrado 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
});
Como os registros do console da biblioteca de Web Vitals aparecem. O console neste exemplo mostra o nome da métrica (INP), o valor de INP (56), onde esse valor está dentro dos limites de INP (bom) e as várias informações mostradas no objeto de atribuição, incluindo entradas da API Long Animation Frames.
Como os dados da biblioteca de Web Vitals aparecem no console.

Além do INP da página, o build de atribuição fornece muitos dados que podem ajudar a entender os motivos das interações lentas, incluindo em qual parte da interação você deve se concentrar. Ele pode ajudar 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 teve o início atrasado? 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 acessar na biblioteca para descobrir algumas causas de lentidão nas interações 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 de cliques, toques ou entradas do teclado.
inputDelay* O atraso de entrada da interação.
processingDuration* O tempo desde que o primeiro listener de eventos começou a ser executado em resposta à interação do usuário até o término de todo o processamento do listener de eventos.
presentationDelay* O atraso da apresentação da interação, que ocorre a partir do momento em que os manipuladores de eventos terminam até o momento em que o frame seguinte é exibido.
longAnimationFrameEntries* Entradas do LoAF associadas à interação. Confira a próxima seção para mais informações.
*Novidade na versão 4

A partir da versão 4 da biblioteca de Web Vitals, é possível ter insights ainda mais aprofundados sobre interações problemáticas com os dados fornecidos com os detalhamentos de fase 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)

Compatibilidade com navegadores

  • Chrome: 123.
  • Edge: 123.
  • Firefox: não é compatível.
  • Safari: não é compatível.

Origem

Resolver problemas de interações usando dados de campo é uma tarefa desafiadora. No entanto, com os dados do LoAF, agora é possível ter insights melhores sobre as causas das interações lentas, já que o LoAF expõe vários tempos detalhados e outros dados que podem ser usados para identificar causas precisas e, mais importante, onde está a origem do problema no código do site.

O build de atribuição da biblioteca de 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 de objeto de entrada do LoAF Dados
duration A duração do frame de animação longo até o layout ser concluído, 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 enfileirado durante o frame. Útil para descobrir o início do atraso de entrada de uma interação.
startTime O carimbo de data/hora inicial do frame.
renderStart Quando o trabalho de renderização do frame começou. Isso inclui todos os callbacks requestAnimationFrame (e callbacks ResizeObserver, se aplicável), mas possivelmente antes do início de qualquer trabalho de estilo/layout.
styleAndLayoutStart Quando o trabalho de estilo/layout ocorre no frame. 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 o INP da página.
Uma visualização de um frame longo de animação de acordo com o modelo LoAF.
Um diagrama dos tempos de um frame de animação longo de acordo com a API LoAF (menos 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 deve ser de particular interesse:

Chave do objeto de atribuição de script Dados
invoker O invocador. Isso pode variar com base no tipo de invocação descrito na próxima linha. Exemplos de invocadores podem ser valores como 'IMG#id.onload', 'Window.requestAnimationFrame' ou 'Response.json.then'.
invokerType O tipo de 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 de animação longa.
sourceCharPosition A posição do caractere no script identificada 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 isso aconteceu.

Medir e identificar causas comuns de interações lentas

Para você ter uma ideia de como usar essas informações, este guia vai explicar como usar os dados do LoAF mostrados na biblioteca web-vitals para determinar algumas causas das interações lentas.

Tempos de processamento longos

A duração do processamento de uma interação é o tempo que leva para que os callbacks de gerenciador de eventos registrados da interação sejam executados até a conclusão e tudo o que possa acontecer entre eles. Durações de processamento altas são exibidas pela biblioteca de 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 demorou muito para ser executado, mas nem sempre é assim. Depois de confirmar que esse é o problema, você pode analisar melhor os dados de 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.sort((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 exata de uma interação com valores de duração de processamento altos, incluindo:

  • O elemento e o listener de eventos registrado.
  • O arquivo de script e a posição do caractere dentro dele 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 foi responsável por valores de duração de processamento altos. Além disso, como os scripts de terceiros geralmente podem registrar os próprios manipuladores de eventos, você pode determinar se o código foi responsável ou não. Para o código que você controla, é recomendável otimizar tarefas longas.

Atrasos longos na entrada

Embora os manipuladores de eventos de longa duração sejam comuns, há outras partes da interação que precisam ser consideradas. Uma parte ocorre antes da duração do processamento, conhecida como atraso de entrada. Esse é o tempo entre o momento em que o usuário inicia a interação e o momento em que os callbacks do gerenciador de eventos começam a ser executados. Isso acontece quando a linha de execução principal já está processando outra tarefa. O build 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 saber se a interação ocorreu durante o carregamento da página ou depois.

Foi durante o carregamento da página?

A linha de execução principal geralmente fica mais ocupada à medida que a página é carregada. Durante esse tempo, vários tipos de tarefas são enfileiradas e processadas. Se o usuário tentar interagir com a página enquanto todo esse trabalho está acontecendo, a interação pode ser atrasada. As páginas que carregam muito JavaScript podem iniciar o trabalho para 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, e você pode 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.sort((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 altos atrasos de entrada e tipos de invocador de 'classic-script' ou 'module-script', será razoável dizer que os scripts do site estão demorando muito para serem avaliados e bloqueando a linha de execução principal por tempo suficiente para atrasar as interações. É possível reduzir esse tempo de bloqueio dividindo os scripts em pacotes menores, adiando o carregamento do código inicialmente não utilizado para um momento posterior e auditando o site em busca de código não utilizado que pode ser removido.

Foi após o carregamento da página?

Embora os atrasos de entrada geralmente ocorram enquanto uma página está sendo carregada, eles também podem ocorrer após o carregamento da página devido a uma causa totalmente diferente. As causas comuns de atrasos de entrada após o carregamento da página podem ser códigos executados periodicamente devido a uma chamada setInterval anterior ou até mesmo callbacks de eventos que foram enfileirados para execução mais cedo e ainda estão em processamento.

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.sort((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'
  }
});

Como acontece com a solução de problemas de valores de duração de processamento elevados, os altos atrasos de entrada devido às causas mencionadas anteriormente fornecerão dados de atribuição de script detalhados. No entanto, o tipo de invocação muda de acordo com a natureza do trabalho que atrasou a interação:

  • 'user-callback' indica que a tarefa de bloqueio foi de setInterval, setTimeout ou até mesmo requestAnimationFrame.
  • 'event-listener' indica que a tarefa de bloqueio foi de uma entrada anterior que foi enfileirada e ainda está em processamento.
  • 'resolve-promise' e 'reject-promise' significam que a tarefa de bloqueio foi de algum trabalho assíncrono 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 por um script de terceiros.

Atrasos longos na apresentação

Os atrasos da apresentação são o último quilômetro de uma interação e começam quando os manipuladores de eventos da interação terminam, até o ponto em que o frame seguinte foi pintado. Eles ocorrem quando o trabalho em um manipulador de eventos devido a uma interação altera o estado visual da interface do usuário. Assim como acontece com as durações de processamento e os atrasos de entrada, a biblioteca web-vitals pode informar quanto tempo durou o atraso da 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 altos na apresentação de interações que contribuem para o INP do seu site, os culpados podem variar, mas aqui estão algumas causas a serem observadas.

Trabalho caro de estilo e layout

Atrasos longos na apresentação podem gerar recálculos de estilo e trabalhos de layout caros devido a várias causas, incluindo seletores de CSS complexos e tamanhos grandes de DOM. É possível medir a duração desse trabalho com os tempos de LoAF mostrados na biblioteca de 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.sort((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'
  }
});

O LoAF não informa a duração do trabalho de estilo e layout para 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 do 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 requestAnimationFrame de longa duração

Uma possível causa de atrasos longos na apresentação é o trabalho excessivo feito em um callback requestAnimationFrame. O conteúdo desse callback é executado depois que os manipuladores de eventos param de ser executados, mas antes do recalculo de estilo e do trabalho de layout.

Esses callbacks podem levar um tempo considerável para serem concluídos se o trabalho neles for complexo. Se você suspeita que valores altos de atraso de apresentação são devidos ao trabalho que está fazendo com requestAnimationFrame, use os dados de LoAF mostrados pela biblioteca de Web Vitals para identificar estes 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.sort((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 da apresentação é gasta em um callback requestAnimationFrame, verifique se o trabalho que você está fazendo nesses callbacks é limitado a realizar trabalho que resulte em uma atualização real da interface do usuário. Qualquer outro trabalho que não afete o 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 usuários reais. Com as ferramentas de coleta de dados de campo, como a biblioteca web-vitals JavaScript (ou um provedor RUM), você tem mais confiança sobre quais interações são mais problemáticas e, depois, reproduza as interações problemáticas no laboratório para começar a corrigi-las.

Imagem principal do Unsplash, por Federico Respini.