Encontrar interações lentas no campo

Saiba como encontrar interações lentas nos dados de campo do seu site para que você possa encontrar oportunidades para melhorar a interação com a próxima exibição.

Dados de campo são dados que informam como os usuários reais estão interagindo com seu site. Ele mostra problemas que não são encontrados apenas nos dados de laboratório. Quando a Interação com a próxima exibição (INP, na sigla em inglês) 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 os dados de campo do Chrome User Experience Report (CrUX, na sigla em inglês) para ver se o site apresenta problemas. Em seguida, você aprenderá a usar o build de atribuição da biblioteca Web-vitals JavaScript e os novos insights que ela fornece da API Long Animation Frames (LoAF) 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 está 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 aceitaram enviar dados de telemetria.

Os dados do CrUX são exibidos em várias áreas diferentes e dependem do escopo das informações 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 e-commerce têm os tipos página de detalhes do produto e página de informações do produto. Você pode conferir 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 que você inserir o URL, os dados dos campos referentes a ele (se disponíveis) serão exibidos para várias métricas, incluindo o INP. Também é possível usar os botões para conferir os valores de INP nas dimensões de dispositivos móveis e computadores.

Dados de campo conforme o CrUX no PageSpeed Insights, mostrando LCP, INP, CLS nas três Core Web Vitals e TTFB, FCP como métricas de diagnóstico e FID como uma métrica Core Web Vitals descontinuada.
Leia os dados do CrUX conforme os insights do PageSpeed. Neste exemplo, o INP da página da Web precisa de melhoria.

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ários reais (RUM, na sigla em inglês) disponíveis que ajudam você a coletar seus próprios dados de campo dos usuários do seu site para ajudar a responder a isso, e uma opção é reunir esses dados de campo 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. Ela pode ser usada para registrar diversas métricas, incluindo o INP em navegadores compatíveis.

Compatibilidade com navegadores

  • 96
  • 96
  • x
  • x

Origem

O build padrão da biblioteca web-vitals pode ser usado para coletar dados INP básicos dos 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 usuários, convém 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 sozinhos não dizem muito mais do que o CrUX. É 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 acessar dos usuários em campo para resolver melhor as interações problemáticas que afetam 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);        // 512
  console.log(rating);       // 'poor'
  console.dir(attribution);  // Attribution data
});
Como aparecem os registros do console da biblioteca web-vitals. O console neste exemplo mostra o nome da métrica (INP), o valor INP (56), em que o valor reside nos limites de INP (bom) e os vários bits de informações mostradas no objeto de atribuição, incluindo entradas da API Long Animation Frame.
Como os dados da biblioteca web-vitals aparecem no console.

Além do próprio INP da página, o build de atribuição fornece muitos dados que podem ser usados para entender os motivos da lentidão, incluindo em qual parte da interação você precisa se concentrar. Ele 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 foi adiado? Nesse caso, 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 de atribuição básicos da biblioteca que podem ajudar você a descobrir algumas causas de alto nível de interações lentas no seu site:

Chave do 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, 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 e até o momento em que todo o processamento do listener de eventos foi concluído.
presentationDelay* O atraso da apresentação da interação, que ocorre do momento em que os manipuladores de eventos terminam até o momento em que o próximo frame é exibido.
longAnimationFrameEntries* Entradas do LoAF associadas à interação. Saiba mais a seguir.
*Novidades na versão 4

A partir da versão 4 da biblioteca web-vitals, você pode ter insights ainda mais detalhados sobre interações problemáticas por meio dos dados que ela fornece com detalhamentos de fase do INP (atraso de entrada, duração do processamento e atraso da apresentação) e a API Long Animation Frame (LoAF).

API Long Animation Frame (LoAF)

Compatibilidade com navegadores

  • 123
  • 123
  • x
  • x

Origem

Depurar interações usando dados de campo é uma tarefa desafiadora. No entanto, com os dados do LoAF, agora é possível obter informações melhores sobre as causas por trás das interações lentas, pois 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 LoAF na chave longAnimationFrameEntries do objeto attribution. A tabela a seguir lista alguns bits importantes de dados que podem ser encontrados em cada entrada do LoAF:

Chave do objeto de entrada do LoAF Dados
duration É a duração do frame longo de animação até o fim do layout, mas excluindo a pintura e a composição.
blockingDuration O tempo total no frame 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 ocorre um trabalho de estilo/layout no frame. 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 que contêm informações de atribuição de script que contribuem para o INP da página.
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 mostrada pelas entradas do LoAF precisa ser interessante:

Chave do 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 para o 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 na tabela, que fornecem informações sobre o script responsável pela interação lenta e como ele foi responsável pela interação lenta.

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

Para dar uma ideia de como essas informações podem ser usadas, este guia agora mostra como usar os dados de LoAF exibidos na biblioteca web-vitals para determinar algumas causas por trás das interações lentas.

Processamento mais longo

A duração do processamento de uma interação é o tempo que leva para os callbacks do manipulador de eventos registrados da interação serem executados até a conclusão e qualquer outra coisa que possa acontecer entre eles. 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 por trás 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, analise 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 é possível observar 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 respectivo listener de eventos registrado.
  • O arquivo de script e a posição dos caracteres 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 é inestimável. Você não precisa mais descobrir exatamente qual interação ou quais manipuladores de eventos foram responsáveis pelos valores de alta duração do processamento. Além disso, como scripts de terceiros geralmente podem registrar os próprios manipuladores de eventos, você pode determinar se foi ou não seu código que foi responsável. Para o código que você controla, recomendamos 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 tempo entre o momento em que o usuário inicia a interação e 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ê perceber que algumas interações têm altos atrasos de entrada, será necessário descobrir o que estava acontecendo na página no momento da interação que causou o longo atraso na entrada. Isso geralmente se resume se a interação ocorreu enquanto a página estava carregando ou depois.

Isso ocorreu durante o carregamento da página?

Geralmente, a linha de execução principal fica mais ocupada enquanto uma página é 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, além de executar funções que preparam uma página para as interações do usuário. Esse trabalho pode atrapalhar a interação do usuário durante a atividade, 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 observar altos atrasos de entrada e tipos de invocadores de 'classic-script' ou 'module-script', é possível dizer que os scripts no seu site estão demorando muito para avaliar e estão bloqueando a linha de execução principal por tempo suficiente para atrasar as interações. É possível reduzir esse tempo de bloqueio dividindo seus scripts em pacotes menores, adiar o carregamento de código inicialmente não utilizado para um momento posterior e auditar seu site em busca de códigos não utilizados que possam ser removidos por completo.

Isso ocorreu depois do carregamento da página?

Embora atrasos de entrada geralmente ocorram durante o carregamento de uma página, é possível que eles ocorram após o carregamento de uma página por uma causa completamente 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 foram colocados na fila para serem executados 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 alta duração de processamento, altos atrasos de entrada devido às causas mencionadas anteriormente fornecerão dados detalhados de atribuição de script. 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 era de setInterval, setTimeout ou até mesmo requestAnimationFrame.
  • 'event-listener' indica que a tarefa de bloqueio veio 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 um 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 noção de onde começar e se o atraso na entrada foi causado pelo seu próprio código ou por um script de terceiros.

Atrasos na apresentação longa

Atrasos na apresentação são a última milha 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 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 do processamento e os atrasos de entrada, a biblioteca web-vitals pode informar o tempo de atraso da apresentação para uma interação:

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {presentationDelay} = attribution; // 113.32307691
});

Se você registrar esses dados e perceber altos atrasos na apresentação para interações que contribuem para o INP do seu site, os culpados podem variar, mas aqui estão algumas causas a serem observadas.

Estilo e layout caros

Atrasos longos na apresentação podem resultar em recálculos de estilo e layout caros de vários motivos, 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 de início, é possível usar outros dados do LoAF para calcular a duração precisa do trabalho, determinando o horário de término do frame e subtraindo o carimbo de data/hora de início do trabalho de estilo e layout.

Callbacks requestAnimationFrame de longa duração

Uma possível causa de longos atrasos na apresentação é o trabalho excessivo feito em um callback requestAnimationFrame. O conteúdo desse callback é executado após a conclusão dos manipuladores de eventos, 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, caso o trabalho feito neles seja complexo. Se você suspeitar que valores altos de atraso na apresentação são resultado do trabalho realizado com requestAnimationFrame, use os dados de LoAF exibidos pela biblioteca 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ê perceber que uma parte significativa do tempo de atraso da apresentação é gasto em um callback requestAnimationFrame, verifique se o trabalho que você está fazendo nesses callbacks está limitado à execução de trabalhos que resultam em uma atualização real na interface do usuário. Qualquer outro trabalho que não toque no DOM ou em estilos de atualização vai atrasar desnecessariamente a pintura do próximo frame, então tenha cuidado.

Conclusão

Os dados de campo são a melhor fonte de informação para entender quais interações são problemáticas para usuários reais no campo. Ao usar 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, em seguida, pode reproduzir interações problemáticas no laboratório e, em seguida, fazer a correção.

Imagem principal do Unsplash, por Federico Respini.