Busque interacciones lentas en el campo

Aprende a encontrar interacciones lentas en los datos del campo de tu sitio web, de modo que puedas encontrar oportunidades para mejorar su interacción con la siguiente pintura.

Los datos de campo son datos que indican la experiencia de los usuarios reales en tu sitio web. Esta función detalla los problemas que no puedes encontrar solo en los datos de lab. En lo que respecta a Interaction to Next Paint (INP), los datos de campo son esenciales para identificar interacciones lentas y proporcionan pistas vitales para ayudarte a solucionarlos.

En esta guía, aprenderás a evaluar rápidamente el INP de tu sitio web con los datos de campo del Chrome User Experience Report (CrUX) para ver si tu sitio web tiene problemas con INP. Más adelante, aprenderás a usar la compilación de atribución de la biblioteca de JavaScript web-vitals, y las nuevas estadísticas que proporciona la API de Long Animation Frames (LoAF), para recopilar e interpretar los datos de campo de las interacciones lentas en tu sitio web.

Comienza con CrUX para evaluar el INP de tu sitio web

Si no recopilas datos de campo de los usuarios de tu sitio web, CrUX puede ser un buen punto de partida. CrUX recopila datos de campo de usuarios reales de Chrome que aceptaron enviar datos de telemetría.

Los datos de CrUX aparecen en varias áreas diferentes y dependen del alcance de la información que estés buscando. CrUX puede proporcionar datos sobre INP y otras Métricas web esenciales para lo siguiente:

  • Páginas individuales y orígenes completos con PageSpeed Insights.
  • Tipos de páginas. Por ejemplo, muchos sitios web de comercio electrónico tienen tipos de página de detalles del producto y de página de ficha de producto. Puedes obtener datos de CrUX para tipos de páginas únicos en Search Console.

Como punto de partida, puedes ingresar la URL de tu sitio web en PageSpeed Insights. Una vez que ingreses la URL, los datos del campo (si están disponibles) se mostrarán para varias métricas, incluido INP. También puedes usar los botones para verificar los valores de INP en las dimensiones de dispositivos móviles y computadoras de escritorio.

Datos de campo, como muestra CrUX en PageSpeed Insights, en el que se muestran los valores de LCP, INP y CLS en las tres Métricas web esenciales, y TTFB, FCP como métricas de diagnóstico y FID como métrica Métricas web esenciales obsoleta.
Lectura de datos de CrUX como se ve en PageSpeed Insights. En este ejemplo, el INP de la página web debe mejorarse.

Estos datos son útiles porque te indican si tienes un problema. Sin embargo, lo que CrUX no puede hacer es indicarte qué está causando el problema. Hay muchas soluciones de supervisión de usuarios reales (RUM) disponibles que te ayudarán a recopilar tus propios datos de campo de los usuarios de tu sitio web para ayudarte a responder esa pregunta. Una opción es recopilar esos datos de campo por tu cuenta usando la biblioteca de JavaScript web-vitals.

Recopila datos de campo con la biblioteca JavaScript web-vitals

La biblioteca JavaScript de web-vitals es una secuencia de comandos que puedes cargar en tu sitio web para recopilar datos de campo de los usuarios de tu sitio web. Puedes usarlo para registrar varias métricas, incluido INP en navegadores que lo admiten.

Navegadores compatibles

  • 96
  • 96
  • x
  • x

Origen

Se puede usar la compilación estándar de la biblioteca web-vitals para obtener datos básicos del INP de los usuarios en el campo:

import {onINP} from 'web-vitals';

onINP(({name, value, rating}) => {
  console.log(name);    // 'INP'
  console.log(value);   // 512
  console.log(rating);  // 'poor'
});

Para analizar los datos de campo de tus usuarios, debes enviar estos datos a alguna parte:

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);
});

Sin embargo, estos datos por sí mismos no dicen mucho más de lo que CrUX. Aquí es donde entra en juego la compilación de atribución de la biblioteca web-vitals.

Llega más lejos con la compilación de atribución de la biblioteca web-vitals

La compilación de atribución de la biblioteca web-vitals muestra datos adicionales que puedes obtener de los usuarios sobre el terreno para ayudarte a solucionar mejor las interacciones problemáticas que afectan el INP de tu sitio web. Se puede acceder a estos datos a través del objeto attribution que aparece en el método onINP() de la 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
});
Cómo aparecen los registros de la consola de la biblioteca web-vitals. La consola en este ejemplo muestra el nombre de la métrica (INP), el valor de INP (56), en el que ese valor reside dentro de los umbrales de INP (bueno) y los distintos bits de información que se muestran en el objeto de atribución, incluidas las entradas de la API de Long Animation Frame.
Cómo aparecen los datos de la biblioteca web-vitals en la consola.

Además del INP de la página, la compilación de atribución proporciona muchos datos que puedes usar para comprender los motivos de las interacciones lentas, como en qué parte de la interacción debes enfocarte. Puede ayudarte a responder preguntas importantes como las siguientes:

  • "¿El usuario interactuó con la página mientras se cargaba?"
  • "¿Los controladores de eventos de la interacción se ejecutaron durante mucho tiempo?"
  • "¿El código del controlador de eventos de interacción se retrasó desde el inicio? Si es así, ¿qué más estaba sucediendo en el subproceso principal en ese momento?"
  • "¿La interacción causó una gran cantidad de trabajo de renderización que retrasó la pintura del siguiente fotograma?"

En la siguiente tabla, se muestran algunos de los datos de atribución básicos que puedes obtener de la biblioteca y que pueden ayudarte a descubrir algunas de las causas principales de las interacciones lentas en tu sitio web:

Clave del objeto attribution Precio de
interactionTarget Un selector de CSS que apunta al elemento que produjo el valor INP de la página, por ejemplo, button#save.
interactionType El tipo de interacción, ya sea a partir de clics, presiones o entradas del teclado.
inputDelay* El retraso de entrada de la interacción.
processingDuration* El tiempo que transcurre desde que el primer objeto de escucha de eventos comenzó a ejecutarse en respuesta a la interacción del usuario hasta que finaliza el procesamiento de todo el objeto de escucha de eventos.
presentationDelay* El retraso de presentación de la interacción, que ocurre desde que finalizan los controladores de eventos hasta que se pinta el siguiente fotograma.
longAnimationFrameEntries* Entradas de la LoAF asociadas con la interacción Consulta la siguiente información para obtener más información.
*Novedades de la versión 4

A partir de la versión 4 de la biblioteca web-vitals, puedes obtener estadísticas aún más detalladas sobre las interacciones problemáticas a través de los datos que proporciona con los desgloses de fases del INP (retraso de entrada, duración del procesamiento y retraso de presentación) y la API de Long Animation Frame (LoAF).

API de Long Animation Frame (LoAF)

Navegadores compatibles

  • 123
  • 123
  • x
  • x

Origen

La depuración de interacciones con datos de campo es una tarea difícil. Sin embargo, con los datos del LoAF, ahora es posible obtener mejores estadísticas sobre las causas de las interacciones lentas, ya que LoAF expone una serie de tiempos detallados y otros datos que puedes usar para identificar causas precisas y, lo que es más importante, dónde se encuentra la fuente del problema en el código de tu sitio web.

La compilación de atribución de la biblioteca web-vitals expone un array de entradas de LoAF en la clave longAnimationFrameEntries del objeto attribution. La siguiente tabla incluye algunos datos clave que puedes encontrar en cada entrada de LoAF:

Clave de objeto de entrada de LoAF Precio de
duration Es la duración del fotograma de animación extenso, hasta el momento en que finaliza el diseño, pero sin incluir la pintura ni la composición.
blockingDuration La cantidad total de tiempo en el fotograma durante el cual el navegador no pudo responder rápidamente debido a tareas largas. Este tiempo de bloqueo puede incluir tareas largas que ejecutan JavaScript, así como cualquier tarea de renderización larga posterior en el fotograma.
firstUIEventTimestamp La marca de tiempo del momento en que el evento estuvo en cola durante el fotograma. Es útil para averiguar el inicio del retraso de entrada de una interacción.
startTime La marca de tiempo del inicio del fotograma.
renderStart Cuándo comenzó el trabajo de renderización del fotograma Esto incluye cualquier devolución de llamada requestAnimationFrame (y devoluciones de llamada ResizeObserver, si corresponde), pero posiblemente antes de que comience cualquier trabajo de diseño o estilo.
styleAndLayoutStart Cuando se trabaja el estilo o diseño en el marco. Puede ser útil para determinar la duración del trabajo de estilo o diseño al buscar otras marcas de tiempo disponibles.
scripts Un array de elementos que contiene información de atribución de secuencia de comandos que contribuye al INP de la página.
Visualización de un fotograma de animación largo según el modelo de LoAF.
Diagrama de los tiempos de un fotograma de animación largo según la API de LoAF (blockingDuration menos).

Toda esta información puede decirte mucho sobre lo que hace que una interacción sea lenta, pero el array scripts que muestran las entradas de LoAF debería ser de especial interés:

Clave de objeto de atribución de secuencia de comandos Precio de
invoker El invocador Esto puede variar según el tipo de invocador que se describe en la siguiente fila. Algunos ejemplos de invocadores pueden ser valores como 'IMG#id.onload', 'Window.requestAnimationFrame' o 'Response.json.then'.
invokerType El tipo de invocador. Puede ser 'user-callback', 'event-listener', 'resolve-promise', 'reject-promise', 'classic-script' o 'module-script'.
sourceURL La URL de la secuencia de comandos de donde se originó el fotograma de animación largo.
sourceCharPosition La posición del carácter en la secuencia de comandos que sourceURL identifica.
sourceFunctionName El nombre de la función en la secuencia de comandos identificada.

Cada entrada del array contiene los datos que se muestran en esta tabla, que te proporciona información sobre la secuencia de comandos responsable de la interacción lenta y de qué manera lo hizo.

Medir e identificar las causas comunes detrás de las interacciones lentas

Para darte una idea de cómo podrías usar esta información, en esta guía se explicará cómo usar los datos de LoAF que aparecen en la biblioteca web-vitals para determinar algunas causas de las interacciones lentas.

Duraciones extensas de procesamiento

La duración del procesamiento de una interacción es el tiempo que tarda en completarse las devoluciones de llamada del controlador de eventos registrados de la interacción y todo lo que pueda ocurrir entre ellas. La biblioteca web-vitals muestra las duraciones de procesamiento altas:

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

onINP(({name, value, attribution}) => {
  const {processingDuration} = attribution; // 512.5
});

Es normal pensar que la causa principal de una interacción lenta es que el código del controlador de eventos tardó demasiado en ejecutarse, pero no siempre es así. Una vez que hayas confirmado que este es el problema, puedes profundizar más en los datos 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 puedes ver en el fragmento de código anterior, puedes trabajar con los datos de LoAF para rastrear la causa precisa de una interacción con valores de duración de procesamiento elevado, incluidos los siguientes:

  • El elemento y su objeto de escucha de eventos registrado
  • El archivo de la secuencia de comandos (y la posición de los caracteres dentro de él) que contiene el código del controlador de eventos de larga duración.
  • Es el nombre de la función.

Este tipo de datos es invaluable. Ya no tienes que encargarte de averiguar exactamente qué interacción (o cuáles de sus controladores de eventos) fueron responsables de los valores de duración de procesamiento altos. Además, debido a que las secuencias de comandos de terceros a menudo pueden registrar sus propios controladores de eventos, puedes determinar si fue tu código el responsable. En el caso del código que controlas, te recomendamos que consideres optimizar tareas largas.

Demoras largas en las entradas

Si bien los controladores de eventos de larga duración son comunes, hay otras partes de la interacción que se deben considerar. Una parte se produce antes de la duración del procesamiento, lo que se conoce como el retraso de entrada. Es el tiempo que transcurre desde que el usuario inicia la interacción hasta el momento en que las devoluciones de llamada del controlador de eventos comienzan a ejecutarse y ocurren cuando el subproceso principal ya está procesando otra tarea. La compilación de atribución de la biblioteca web-vitals puede indicarte la duración de la demora de entrada para una interacción:

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

onINP(({name, value, attribution}) => {
  const {inputDelay} = attribution; // 125.59439536
});

Si notas que algunas interacciones tienen grandes retrasos en las entradas, deberás averiguar qué estaba sucediendo en la página en el momento de la interacción que causó la larga demora de entrada (que, a menudo, se reduce a si la interacción ocurrió mientras se cargaba la página o después).

¿Fue durante la carga de la página?

El subproceso principal suele estar más ocupado mientras se carga una página. Durante este tiempo, todo tipo de tareas se ponen en cola y se procesan, y si el usuario intenta interactuar con la página mientras todo este trabajo ocurre, puede retrasar la interacción. Las páginas que cargan una gran cantidad de JavaScript pueden iniciar trabajo para compilar y evaluar secuencias de comandos, así como ejecutar funciones que preparan una página para las interacciones del usuario. Este trabajo puede interferir si el usuario interactúa a medida que ocurre esta actividad, y puedes averiguar si ese es el caso de los usuarios de tu sitio web:

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

Si registras estos datos en el campo y observas grandes demoras en las entradas y tipos de invocadores de 'classic-script' o 'module-script', es justo decir que las secuencias de comandos de tu sitio tardan mucho tiempo en evaluarse y que bloquean el subproceso principal el tiempo suficiente como para retrasar las interacciones. Para reducir este tiempo de bloqueo, puedes dividir tus secuencias de comandos en paquetes más pequeños, diferir la carga del código que inicialmente no se usó para que se cargue más adelante y auditar tu sitio en busca de código sin usar que puedas quitar por completo.

¿Fue después de cargar la página?

Si bien los retrasos en la entrada suelen producirse mientras se carga una página, es posible que ocurran después de que se cargue la página, debido a una causa completamente diferente. Las causas comunes de retrasos en las entradas después de la carga de la página pueden ser código que se ejecuta periódicamente debido a una llamada setInterval anterior o incluso devoluciones de llamada de eventos que estaban en cola para ejecutarse antes y que aún se están procesando.

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 en el caso de la solución de problemas de valores de duración de procesamiento elevado, las demoras altas en las entradas debido a las causas mencionadas anteriormente te proporcionarán datos detallados de atribución de la secuencia de comandos. Sin embargo, lo que cambia es que el tipo de invocador cambiará según la naturaleza del trabajo que retrasó la interacción:

  • 'user-callback' indica que la tarea de bloqueo era de setInterval, setTimeout o incluso requestAnimationFrame.
  • 'event-listener' indica que la tarea de bloqueo provenía de una entrada anterior que estaba en cola y aún se está procesando.
  • 'resolve-promise' y 'reject-promise' significan que la tarea de bloqueo provenía de algún trabajo asíncrono que se inició anteriormente y que se resolvió o se rechazó cuando el usuario intentó interactuar con la página, lo que demoró la interacción.

En cualquier caso, los datos de atribución de secuencias de comandos te darán una idea de dónde comenzar a buscar y si la demora en las entradas se debió a tu propio código o a la de una secuencia de comandos de terceros.

Demoras largas en la presentación

Los retrasos en la presentación son el último tramo de una interacción y comienzan cuando terminan los controladores de eventos de la interacción, hasta el punto en que se pintó el siguiente fotograma. Se producen cuando el trabajo en un controlador de eventos debido a una interacción cambia el estado visual de la interfaz de usuario. Al igual que con las duraciones de procesamiento y los retrasos de entrada, la biblioteca web-vitals puede indicarte cuánto tiempo fue el retraso de presentación para una interacción:

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

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

Si registras estos datos y observas grandes demoras en la presentación de las interacciones que contribuyen al INP de tu sitio web, las causas pueden variar, pero debes tener en cuenta algunas causas.

Trabajo de diseño y estilo costoso

Las demoras prolongadas en la presentación pueden ser costosos para el recalcular el estilo y el trabajo de diseño que surgen por diversas causas, incluidos los selectores CSS complejos y los tamaños grandes del DOM. Puedes medir la duración de este trabajo con los tiempos de LoAF que aparecen en la 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'
  }
});

LoAF no te indicará la duración del trabajo de diseño y estilo para un fotograma, pero te indicará cuándo comenzó. Con esta marca de tiempo de inicio, puedes usar otros datos de LoAF para calcular una duración precisa de ese trabajo determinando la hora de finalización del fotograma y restando la marca de tiempo de inicio del diseño y el trabajo de diseño a esa marca de tiempo.

Devoluciones de llamada de requestAnimationFrame de larga duración

Una posible causa de las demoras prolongadas en la presentación es el trabajo excesivo en una devolución de llamada requestAnimationFrame. El contenido de esta devolución de llamada se ejecuta después de que los controladores de eventos terminan de ejecutarse, pero justo antes de volver a calcular el estilo y trabajar con el diseño.

Estas devoluciones de llamada pueden demorar bastante tiempo en completarse si el trabajo realizado dentro de ellas es complejo. Si sospechas que los valores altos de retraso en la presentación se deben al trabajo que realizas con requestAnimationFrame, puedes usar los datos de LoAF que muestra la biblioteca web-vitals para identificar estas situaciones:

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

Si ves que una parte significativa del tiempo de retraso de la presentación se dedica a una devolución de llamada requestAnimationFrame, asegúrate de que el trabajo que estés haciendo en esas devoluciones de llamada se limite a realizar trabajos que generen una actualización real de la interfaz de usuario. Cualquier otra tarea que no toque el DOM o no actualice los estilos retrasará innecesariamente la pintura del siguiente fotograma, así que ten cuidado.

Conclusión

Los datos de campo son la mejor fuente de información a la que puedes recurrir para comprender qué interacciones son problemáticas para los usuarios reales en el campo. Si confías en las herramientas de recopilación de datos de campo, como la biblioteca de JavaScript web-vitals (o un proveedor de RUM), podrás saber con mayor seguridad qué interacciones son las más problemáticas y, luego, pasar a reproducir interacciones problemáticas en el lab y, luego, solucionarlas.

Hero image de Unsplash, de Federico Respini.