Identificar as causas de uma INP ruim

A baixa responsividade às interações dá a impressão de que a página é lenta ou até mesmo completamente corrompida. A Interaction to Next Paint (INP) é uma métrica baseada na API Event Timing. Ela mede a pior interação (menos alguns outliers) como uma medida da capacidade de resposta da página.

Identificar as causas raiz de uma página da Web que não responde pode ser difícil, especialmente porque isso depende das interações do usuário e das condições do ambiente, como os recursos do dispositivo e as condições da rede. Isso torna o diagnóstico ainda mais difícil em comparação com um cenário mais repetível e previsível, como o carregamento de página. Os dados de laboratório replicam apenas um pequeno subconjunto de cenários de usuários reais. Por isso, é essencial medir as causas da INP lenta em campo.

A API Event Timing permite dividir a duração da INP em três subpartes: atraso de entrada (processamento que já está acontecendo quando a interação ocorre), duração do processamento (atrasos resultantes diretamente da interação) e atraso de apresentação (atrasos resultantes da renderização do próximo frame após a interação). Isso ajuda a identificar se o problema está em outro código, no código JavaScript da interação ou nas atualizações de renderização do processamento do navegador, respectivamente, em vez de pular direto para o código da interação.

É possível coletar mais insights sobre o JavaScript, o recálculo de estilo e o trabalho de layout que atrasam uma interação. Um trace de desempenho completo usando a API JS Self-Profiling é uma solução pesada que pode causar problemas de desempenho. A API Long Animation Frames é uma API leve que pode ser usada em campo para identificar JavaScripts lentos e distinguir entre custos de recalcular estilo e layout durante interações de INP:

  • Estilo e layout forçados no nível do script (forcedStyleDuration e forcedLayoutDuration): informa recálculos de estilo síncronos (como chamar getComputedStyle() após mutações do DOM) e layouts/refluxos forçados (como ler getBoundingClientRect() ou offsetWidth após mutações) em um script em execução durante o atraso de entrada ou a duração do processamento.
  • Estilo e layout de renderização no nível do frame (styleDuration e layoutDuration): informa o tempo gasto recalculando estilos em comparação com a atualização do layout durante a atualização de renderização do frame, ajudando a explicar o alto atraso de apresentação.

É fundamental distinguir o recálculo de estilo do layout, porque eles têm causas raiz e correções diferentes: uma duração de estilo alta indica uma correspondência de seletor de CSS cara ou uma invalidação de estilo ampla (corrigida simplificando os seletores ou reduzindo o escopo da regra), enquanto uma duração de layout alta indica cálculos de geometria caros ou thrashing de layout síncrono (corrigido agrupando leituras de DOM antes de gravações, simplificando a estrutura do DOM ou usando contenção de CSS).

Como fazer a implementação

Calcular a INP usando a API Event Timing bruta é complicado e tem várias nuances. Recomendamos usar uma ferramenta ou biblioteca de RUM para coletar esses dados.

web-vitals é uma biblioteca de código aberto do Google que calcula as Core Web Vitals, incluindo a INP, e também inclui subpartes e dados de frames longos de animação para a interação da INP.

Receber atribuições para interações de INP usando a biblioteca web-vitals

A biblioteca web-vitals é uma biblioteca pequena usada para medir as Core Web Vitals e outras métricas de performance. A função onINP() pode ser usada para identificar a interação mais lenta e inclui informações sobre as subpartes da INP, os scripts executados durante a interação e as durações de estilo e layout usando a API Long Animation Frames.

// Use the attribution build to get Long Animation Frame data
// alongside the INP metric value.
import { onINP } from 'web-vitals/attribution';

onINP((metric) => {
  const { attribution } = metric;
  const longestScript = attribution.longestScript;

  // Sum rendering-phase style and layout durations across all LoAF entries
  // intersecting the INP interaction (primarily explains presentationDelay).
  let renderStyleDuration = 0;
  let renderLayoutDuration = 0;
  for (const entry of attribution.longAnimationFrameEntries) {
    renderStyleDuration += entry.styleDuration ?? 0;
    renderLayoutDuration += entry.layoutDuration ?? 0;
  }

  // Beacon script, style, and layout attribution for the INP interaction,
  // so you can identify the root cause in production.
  navigator.sendBeacon(
    '/analytics',
    JSON.stringify({
      name: 'INP',
      value: metric.value,
      // These fields give the INP subparts:
      inputDelay: attribution.inputDelay,
      presentationDelay: attribution.presentationDelay,
      processingDuration: attribution.processingDuration,
      interactionTarget: attribution.interactionTarget,
      // These fields identify which script function was responsible
      // for the longest processing during the INP interaction.
      invokerType: longestScript?.entry?.invokerType,
      sourceURL: longestScript?.entry?.sourceURL,
      sourceFunctionName: longestScript?.entry?.sourceFunctionName,
      sourceCharPosition: longestScript?.entry?.sourceCharPosition,
      // subpart indicates which phase (input delay, processing, or
      // presentation delay) the longest script overlapped with most.
      subpart: longestScript?.subpart,
      intersectingDuration: longestScript?.intersectingDuration,
      // Synchronous style and layout work forced inside the longest script
      // (with forcedStyleAndLayoutDuration as a combined fallback):
      forcedStyleDuration: longestScript?.entry?.forcedStyleDuration,
      forcedLayoutDuration: longestScript?.entry?.forcedLayoutDuration,
      forcedStyleAndLayoutDuration: longestScript?.entry?.forcedStyleAndLayoutDuration,
      // Rendering-phase style and layout work across the interaction's frames
      // (with totalStyleAndLayoutDuration as a combined fallback):
      renderStyleDuration,
      renderLayoutDuration,
      totalStyleAndLayoutDuration: attribution.totalStyleAndLayoutDuration
    })
  );
});

Práticas recomendadas

  • RECOMENDAMOS usar as subpartes da INP inicialmente para identificar se o atraso é o JavaScript que já está em execução (atraso de entrada), o JavaScript dos manipuladores de eventos para a interação (duração do processamento) ou a renderização subsequente (atraso de apresentação).
  • TENTE identificar o maior JavaScript de bloqueio como detalhe adicional, principalmente para atraso de entrada e duração do processamento.
  • FAÇA a distinção entre o recálculo de estilo (styleDuration, forcedStyleDuration) e o layout (layoutDuration, forcedLayoutDuration) ao diagnosticar uma alta duração de processamento ou atraso na apresentação: tempos de estilo altos indicam seletores de CSS caros ou invalidações amplas, enquanto tempos de layout altos indicam gargalos de geometria do DOM ou thrashing de layout síncrono.
  • DÊ preferência à API Long Animation Frames para fornecer esses detalhes em vez de alternativas como a API JS Self-Profiling, que tem uma sobrecarga de execução maior.
  • USE a biblioteca web-vitals se não houver outra solução de RUM em vigor. Ele pode identificar a interação da INP e inclui informações sobre as subpartes e os scripts que foram executados durante a interação (usando a API Long Animation Frames).
  • RECOMENDADO: envie as informações necessárias para um serviço de análise em vez de apenas registrá-las localmente.

Suporte ao navegador e estratégias de substituição

A API Event Timing está disponível na maioria dos navegadores mais recentes e é necessária para calcular a INP e as subpartes dela. Para navegadores que não são compatíveis com essa API, não é possível medir a INP.

A API Long Animation Frames fornece detalhes adicionais opcionais. Ele pode ser usado com segurança sem substitutos. Na maioria dos casos, as oportunidades de performance identificadas também se aplicam a outros navegadores.

Se o destino de referência for compatível com a API Long Animation Frames (long-animation-frames), mas não com propriedades separadas de duração de estilo e layout (styleDuration e layoutDuration em PerformanceLongAnimationFrameTiming ou forcedStyleDuration e forcedLayoutDuration em PerformanceScriptTiming), use os tempos combinados de estilo e layout:

  • Use script.forcedStyleAndLayoutDuration em PerformanceScriptTiming para estilo forçado síncrono e trabalho de layout dentro de scripts.
  • Use metric.attribution.totalStyleAndLayoutDuration em web-vitals/attribution (ou calcule (entry.paintTime || entry.startTime + entry.duration) - entry.styleAndLayoutStart em cada entrada PerformanceLongAnimationFrameTiming) para combinar o trabalho de estilo e layout.