Encontrar interações lentas no campo

Saiba como encontrar interações lentas nos dados de campo do seu site para encontrar oportunidades de melhorar a interação com a Next Paint.

Os dados de campo informam como os usuários reais estão interagindo com seu site. Ele identifica problemas que você não consegue encontrar apenas 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ê aprenderá a avaliar rapidamente o INP do seu site usando dados de campo do Chrome User Experience Report (CrUX, na sigla em inglês) para ver se o site 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 optaram por enviar dados de telemetria.

Os dados do CrUX são exibidos em várias áreas diferentes e dependem do escopo da informação que você está procurando. O CrUX pode fornecer dados sobre 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 comércio eletrônico têm os tipos Página de detalhes do produto e Página de lista de produtos. Você pode acessar 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 de inserir o URL, os dados dos campos dele (se disponíveis) serão exibidos para várias métricas, incluindo INP. Você também pode usar as alternâncias para verificar seus valores de INP para as dimensões de dispositivos móveis e computadores.

Dados de campo mostrados pelo CrUX no PageSpeed Insights, mostrando LCP, INP, CLS nas três Core Web Vitals, TTFB, FCP como métricas de diagnóstico e FID como uma das Core Web Vitals descontinuadas.
Uma leitura de dados do CrUX conforme visto nas informações do PageSpeed. Neste exemplo, o INP da página da Web precisa ser melhorado.

Esses dados são úteis porque informam se você tem um problema. No entanto, o que o CrUX não consegue fazer é informar 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 diversas métricas, incluindo o INP nos navegadores compatíveis.

Compatibilidade com navegadores

  • Chrome: 96.
  • Borda: 96.
  • Firefox: incompatí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 web-vitals.

Avance ainda mais com o build de atribuição da biblioteca web-vitals

O build de atribuição da biblioteca web-vitals mostra mais dados 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 são exibidos os registros do console da biblioteca web-vitals. 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 web-vitals aparecem no console.

Além do INP em si da página, a criação da atribuição fornece muitos dados que podem ser usados para ajudar a entender os motivos das interações lentas, incluindo em qual parte da interação você deve se concentrar. Ela pode ajudar a responder 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 receber da biblioteca e que podem ajudar a descobrir algumas causas gerais 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 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é quando todo o processamento do listener de eventos foi concluído.
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 mais informações a seguir.
*Novidade na versão 4

A partir da versão 4 da biblioteca web-vitals, é possível ter insights ainda mais detalhados sobre interações problemáticas por meio dos dados fornecidos com detalhamentos da 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, na sigla em inglês)

Compatibilidade com navegadores

  • Chrome: 123
  • Borda: 123.
  • Firefox: incompatível.
  • Safari: incompatível.

Origem

Depurar interações usando dados de campo é uma tarefa desafiadora. Com os dados do LoAF, no entanto, agora é possível obter melhores insights sobre as causas por trás das interações lentas, já que o LoAF expõe uma série de tempos detalhados e outros dados que você pode usar para identificar causas precisas e, mais importante, onde a origem do problema está no código do seu site.

O build de atribuição da biblioteca web-vitals expõe uma matriz de entradas do LoAF na chave longAnimationFrameEntries do objeto attribution. A tabela a seguir lista alguns dados importantes que podem ser encontrados em cada entrada do LoAF:

Chave de objeto de entrada do LoAF Dados
duration A duração do frame longo da animação até a conclusão do layout, exceto 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 inicial do frame.
renderStart Quando o trabalho de renderização do frame começou. Isso inclui todos os callbacks de requestAnimationFrame (e ResizeObserver, se aplicável), mas potencialmente antes de qualquer trabalho de estilo/layout começar.
styleAndLayoutStart Quando o estilo/layout do frame ocorre. Pode ser útil para descobrir a duração do trabalho de estilo/layout ao descobrir outros carimbos de data/hora disponíveis.
scripts Uma matriz de itens contendo informações de atribuição de script que contribuem para o INP da página.
Visualização de um longo frame 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 de objeto de atribuição do 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 identificada por sourceURL.
sourceFunctionName O nome da função no script identificado.

Cada entrada na matriz contém os dados mostrados na tabela, que fornece informações sobre o script que foi responsável pela interação lenta e como ele foi responsável.

Medir e identificar as causas comuns por trás das interações lentas

Para dar uma ideia de como você pode usar essas informações, este guia vai mostrar como usar os dados do LoAF exibidos na biblioteca web-vitals para determinar algumas causas por trás de interações lentas.

Processamentos de longa duração

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 tudo o mais que possa acontecer entre eles. As altas durações de processamento são exibidas 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 é assim. Depois de confirmar que esse é o problema, você pode ir mais fundo 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.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 do LoAF para rastrear a causa precisa por trás de uma interação com valores de alta duração de processamento, 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 dados é inestimável. Você não precisa mais ter o trabalho de descobrir exatamente qual interação ou quais manipuladores de eventos foram responsáveis por altos valores de duração de processamento. Além disso, como os scripts de terceiros muitas vezes podem registrar seus próprios manipuladores de eventos, você pode determinar se o seu código foi o responsável. Para o código que você tem controle, convém analisar a otimização de tarefas longas.

Atrasos de entrada longos

Embora 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, conhecida como atraso de entrada. Esse é o momento entre a interação do usuário, o momento em que os callbacks do manipulador de eventos começam a ser executados e 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 altos atrasos de entrada, precisará descobrir o que estava acontecendo na página no momento da interação que causou o longo atraso na entrada. Isso geralmente se resume ao fato de a interação ter ocorrido durante ou depois do carregamento da página.

Foi durante o carregamento da página?

Geralmente, a linha de execução principal é mais ocupada enquanto uma página está sendo carregada. Durante esse período, todos os tipos de tarefas são colocados em fila e processados, e se o usuário tentar interagir com a página enquanto todo esse trabalho está acontecendo, isso poderá atrasar a interação. As páginas que carregam muito JavaScript podem iniciar o trabalho de compilação e avaliação de scripts, bem como 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, adiar o carregamento inicial de códigos não utilizados e auditar seu site para detectar códigos não utilizados que possam ser totalmente removidos.

Foi depois do carregamento da página?

Embora atrasos de entrada geralmente ocorram durante o carregamento de uma página, eles podem ocorrer depois do carregamento da página por 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é callbacks de eventos que estavam na fila para execução anteriormente 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, a diferença é que o tipo de invocador muda com base na natureza do trabalho que atrasou a interação:

  • 'user-callback' indica que a tarefa de bloqueio foi originada de setInterval, setTimeout ou até mesmo de requestAnimationFrame.
  • 'event-listener' indica que a tarefa de bloqueio era de uma entrada anterior que estava na fila e ainda em processamento.
  • 'resolve-promise' e 'reject-promise' significa 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 de script darão uma noção de por onde começar e se o atraso de entrada se deve ao código próprio ou a 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 qual foi o tempo de 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 observar grandes atrasos de apresentação para interações que contribuem para o INP do seu site, as causas podem variar, mas aqui estão algumas causas a serem observadas.

Estilo e layout caros

Atrasos longos na apresentação podem gerar recálculos de estilo e trabalhos de layout caros decorrentes de várias causas, incluindo seletores de CSS complexos e tamanhos grandes de DOM. Você pode medir a duração desse trabalho com os tempos de LoAF exibidos 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.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 informará a duração do trabalho de estilo e layout para um frame, mas informará quando ele começou. Com esse carimbo de data/hora inicial, você pode 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 estilo e do trabalho de layout deste.

Callbacks de requestAnimationFrame de longa duração

Uma possível causa de atrasos na apresentação é o trabalho excessivo feito em um callback do requestAnimationFrame. O conteúdo desse retorno de chamada é executado depois que os manipuladores de eventos terminam a execução, mas logo 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ê suspeita que valores altos de atraso da apresentação ocorrem devido ao trabalho que está fazendo com o requestAnimationFrame, use os dados do LoAF exibidos 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.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ê perceber que uma parte significativa do tempo de atraso da apresentação é gasto em um callback requestAnimationFrame, verifique se o trabalho realizado nesses callbacks está limitado à execução de trabalhos que resultem em uma atualização real da interface do usuário. Qualquer outro trabalho que não afete o DOM nem os estilos de atualização atrasará desnecessariamente a pintura do próximo frame. Por isso, tenha cuidado.

Conclusão

Os dados de campo são a melhor fonte de informações para você entender quais interações são problemáticas para os usuários reais no campo. 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.