Busque interacciones lentas en el campo

Obtén información para encontrar interacciones lentas en los datos de campo de tu sitio web y oportunidades de mejorar su interacción con el siguiente procesamiento de imagen.

Los datos de campo son datos que indican la experiencia que tienen los usuarios reales de tu sitio web. Muestra avances de problemas que no puedes encontrar únicamente 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 corregirlas.

En esta guía, aprenderás a evaluar rápidamente el INP de tu sitio web con los datos de campo del Informe sobre la experiencia del usuario en Chrome (CrUX) para ver si tu sitio web tiene problemas con INP. Posteriormente, 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 datos de campo de 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 una variedad de áreas y dependen del alcance de la información que busques. 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 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 el INP. También puedes usar los botones de activación para verificar los valores de INP de las dimensiones móviles y de escritorio.

Los datos de campo, como CrUX en PageSpeed Insights, muestran 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 de Métricas web esenciales obsoleta.
Lectura de datos de CrUX como se ve en PageSpeed Insights. En este ejemplo, se debe mejorar el INP de la página web.

Estos datos son útiles porque te indican si tienes un problema. Sin embargo, lo que CrUX no puede hacer es decirte qué está causando los problemas. 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 tú mismo usando la biblioteca JavaScript web-vitals.

Recopila datos de campos con la biblioteca de JavaScript web-vitals

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

Navegadores compatibles

  • 96
  • 96
  • x
  • x

Origen

La compilación estándar de la biblioteca web-vitals se puede utilizar para obtener datos de INP básicos 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, envía estos datos a algún 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);
});

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

Aprovecha al máximo la compilación de atribución de la biblioteca de web-vitals

La compilación de atribución de la biblioteca de web-vitals muestra datos adicionales que puedes obtener de los usuarios en el campo para ayudarte a solucionar mejor los problemas de 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);        // 512
  console.log(rating);       // 'poor'
  console.dir(attribution);  // Attribution data
});
Cómo aparecen los registros de la consola de la biblioteca web-vitals. La consola de este ejemplo muestra el nombre de la métrica (INP), el valor de INP (56), donde ese valor reside dentro de los umbrales de INP (buena) y los distintos bits de información que se muestran en el objeto de atribución, incluidas las entradas de la API de The Long Animation Frame.
Cómo aparecen los datos de la biblioteca de 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, incluida la parte de la interacción en la que 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 por mucho tiempo?"
  • "¿Se retrasó el inicio del código del controlador de eventos de interacción? Si es así, ¿qué más estaba sucediendo en el subproceso principal en ese momento?"
  • "¿La interacción causó mucho trabajo de renderización que retrasó la pintura del siguiente fotograma?"

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

Clave de objeto attribution Precio de
interactionTarget Un selector CSS que apunta al elemento que produjo el valor de INP de la página, por ejemplo, button#save.
interactionType Indica el tipo de interacción, ya sea por clics, presiones o entradas del teclado.
inputDelay* El retraso de entrada de la interacción.
processingDuration* Es el tiempo que transcurre desde el momento en que el primer objeto de escucha de eventos comenzó a ejecutarse en respuesta a la interacción del usuario hasta que finaliza todo el procesamiento del objeto de escucha de eventos.
presentationDelay* El retraso de presentación de la interacción, que ocurre desde el momento en que los controladores de eventos finalizan hasta el momento en 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.
*Novedad 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 de las interacciones problemáticas a través de los datos que proporciona con desgloses de las fases de INP (retraso de entrada, duración del procesamiento y retraso de la 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 usando datos de campo es una tarea difícil. Sin embargo, con los datos de la LoAF, ahora es posible obtener mejores estadísticas sobre las causas de las interacciones lentas, ya que la LoAF expone varios tiempos detallados y otros datos que puedes usar para identificar causas precisas y, lo que es más importante, dónde está 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. En la siguiente tabla, se mencionan algunos bits clave de datos que puedes encontrar en cada entrada de la LOAF:

Clave de objeto de entrada de la LOAF Precio de
duration Es la duración del fotograma de animación largo, hasta el momento en que ha finalizado el diseño, pero sin incluir la pintura ni la composición.
blockingDuration Es la cantidad total de tiempo en el fotograma que el navegador no pudo responder rápidamente debido a tareas largas. Este tiempo de bloqueo puede incluir tareas prolongadas en ejecución de JavaScript, así como cualquier tarea de renderización larga posterior en el fotograma.
firstUIEventTimestamp La marca de tiempo del momento en que se puso en cola el evento durante el fotograma Es útil para averiguar el inicio del retraso de entrada de una interacción.
startTime La marca de tiempo de inicio del fotograma
renderStart Es el momento en que comenzó el trabajo de renderización del fotograma. Esto incluye las devoluciones de llamada requestAnimationFrame (y ResizeObserver, si corresponde), pero posiblemente antes de que comience cualquier trabajo de estilo o diseño.
styleAndLayoutStart Cuando se realizan trabajos de estilo o diseño en el marco. Puede ser útil para determinar la longitud del trabajo de estilo o diseño cuando se establecen otras marcas de tiempo disponibles.
scripts Una matriz de elementos que contiene información de atribución de secuencias 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 (menos blockingDuration).

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

Clave de objeto de atribución de la 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 Es el tipo del invocador. Puede ser 'user-callback', 'event-listener', 'resolve-promise', 'reject-promise', 'classic-script' o 'module-script'.
sourceURL Es la URL de la secuencia de comandos de la que se originó el marco 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, la cual proporciona información sobre la secuencia de comandos responsable de la interacción lenta y sobre cómo lo hizo.

Medir e identificar las causas comunes de las interacciones lentas

Para darte una idea de cómo puedes usar esta información, en esta guía, se explica cómo usar los datos de LoAF que se muestran en la biblioteca web-vitals para determinar las causas de las interacciones lentas.

Larga duración de procesamiento

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

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

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

Es natural 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 en los datos de la 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 datos de LoAF para rastrear la causa exacta detrás de una interacción con valores de duración de procesamiento altos, incluidas las siguientes acciones:

  • El elemento y su objeto de escucha de eventos registrado.
  • El archivo de 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 es necesario el trabajo de descubrir exactamente qué interacción (o cuál de sus controladores de eventos) fue responsable de los valores de duración de procesamiento elevados. 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 o no. En el caso del código que controlas, te recomendamos que consultes la optimización de tareas largas.

Retrasos prolongados en las entradas

Si bien los controladores de eventos de ejecución prolongada son comunes, hay otras partes de la interacción que se deben considerar. Una parte ocurre antes de la duración del procesamiento, que se conoce como 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 de su controlador de eventos comienzan a ejecutarse y ocurre cuando el subproceso principal ya está procesando otra tarea. La compilación de atribución de la biblioteca de web-vitals puede indicarte la duración de la demora de entrada en 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 demoras en la entrada, deberás averiguar qué estaba sucediendo en la página al momento de la interacción que causó la larga demora en la entrada. A menudo, eso se reduce a si la interacción ocurrió mientras se cargaba la página o después.

¿Ocurrió mientras se cargaba la página?

El subproceso principal suele estar más activo mientras se carga la 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 se realiza todo este trabajo, se puede retrasar la interacción. Las páginas que cargan mucho JavaScript pueden iniciar el trabajo de compilación y evaluación de secuencias de comandos, además de ejecutar funciones que preparan una página para las interacciones del usuario. Este trabajo puede interferir si el usuario interactúa mientras 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 retrasos en las entradas y tipos de invocadores de 'classic-script' o 'module-script', es razonable decir que las secuencias de comandos de tu sitio tardan mucho tiempo en evaluar y bloquean el subproceso principal el tiempo suficiente para retrasar las interacciones. Puedes reducir este tiempo de bloqueo dividiendo tus secuencias de comandos en paquetes más pequeños, aplazar el código que inicialmente no se usa para que se cargue más tarde y auditar tu sitio para detectar el código sin usar que puedas quitar por completo.

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

Si bien las demoras en la entrada de texto suelen ocurrir mientras se carga una página, es posible que ocurran después de que se cargue, debido a una causa completamente diferente. Algunas causas comunes de demoras 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 a setInterval anterior o incluso devoluciones de llamadas de eventos que estaban en cola para ejecutarse antes y 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 es el caso de la solución de problemas con valores de duración de procesamiento altos, las grandes demoras en las entradas debido a las causas mencionadas anteriormente le proporcionarán datos detallados de atribución de secuencias de comandos. Sin embargo, la diferencia 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 era de una entrada anterior que estaba en cola y aún se estaba procesando.
  • 'resolve-promise' y 'reject-promise' significan que la tarea de bloqueo provino de un trabajo asíncrono que se inició antes y se resolvió o rechazó en un momento en que el usuario intentó interactuar con la página, lo que retrasó la interacción.

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

Demoras largas en una presentación

Los retrasos en la presentación son el último kilómetro de una interacción y comienzan cuando finalizan los controladores de eventos de la interacción, hasta el momento en que se pinta 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 la duración del procesamiento y las demoras en la entrada, la biblioteca de web-vitals puede indicarte cuánto tiempo tardó la presentación en una interacción:

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

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

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

Trabajo de diseño y estilo costoso

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

La LOAF no te dirá que la duración del trabajo de estilo y diseño corresponde a un fotograma, pero te indicará cuándo comenzó. Con esta marca de tiempo inicial, 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 estilo y el trabajo de diseño.

Devoluciones de llamada de requestAnimationFrame de larga duración

Una posible causa de los retrasos largos en la presentación es el trabajo excesivo que se realiza en una devolución de llamada requestAnimationFrame. El contenido de esta devolución de llamada se ejecuta una vez que los controladores de eventos terminan de ejecutarse, pero justo antes del recálculo de estilo y el trabajo de diseño.

Estas devoluciones de llamada pueden tardar bastante en completarse si el trabajo realizado dentro de ellas es complejo. Si sospechas que los valores altos de demora en la presentación se deben al trabajo que estás haciendo con requestAnimationFrame, puedes usar los datos de LoAF que ofrece la biblioteca de 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 observas 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 realices en estas 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 actualice los estilos retrasará innecesariamente la pintura del siguiente fotograma, por lo que debes tener cuidado.

Conclusión

Los datos de campo son la mejor fuente de información que puedes utilizar cuando se trata de comprender qué interacciones son problemáticas para los usuarios reales en el campo. Si usas herramientas de recopilación de datos de campo, como la biblioteca JavaScript web-vitals (o un proveedor de RUM), puedes tener más seguridad sobre qué interacciones son más problemáticas. Luego, puedes pasar a reproducir interacciones problemáticas en el lab y, luego, corregirlas.

Hero image de Unsplash, de Federico Respini.