Busque interacciones lentas en el campo

Descubre cómo encontrar interacciones lentas en los datos de campo de tu sitio web para 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. Detecta problemas que no puedes encontrar solo en los datos del 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 la 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 la INP. Luego, aprenderás a usar la compilación de atribución de la biblioteca de JavaScript de web-vitals y las nuevas estadísticas que proporciona desde la API de Long Animation Frames (LoAF) para recopilar e interpretar datos de campo para 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 se muestran en varias áreas diferentes y dependen del alcance de la información que buscas. 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 de campo correspondientes (si están disponibles) se mostrarán para varias métricas, incluida la 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 que muestra CrUX en PageSpeed Insights, que muestran LCP, INP y CLS en las tres Métricas web esenciales, TTFB y FCP como métricas de diagnóstico, y FID como una métrica de Métricas web esenciales obsoleta.
Lectura de los 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 decirte qué está causando problemas. Existen 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 responder a esa pregunta. Una opción es recopilar esos datos de campo por tu cuenta con la biblioteca de JavaScript de web-vitals.

Recopila datos de campo con la biblioteca de 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, incluida la INP en los navegadores que la admiten.

Navegadores compatibles

  • Chrome: 96.
  • Borde: 96.
  • Firefox: No es compatible.
  • Safari: No se admite.

Origen

La compilación estándar de la biblioteca de web-vitals se puede usar 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, 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í solos no te brindan mucha más información que CrUX. Ahí es donde entra en juego la compilación de atribución de la biblioteca de Web Vitals.

Obtén más información sobre la compilación de atribución de la biblioteca de web-vitals

La compilación de atribución de la biblioteca de métricas web esenciales muestra datos adicionales que puedes obtener de los usuarios en el campo 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 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 (bueno) y los diversos datos que se muestran en el objeto de atribución, incluidas las entradas de la API de Long Animation Frames.
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 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 Datos
interactionTarget Un selector CSS que apunta al elemento que produjo el valor de INP de la página (por ejemplo, button#save)
interactionType Es el tipo de interacción, ya sea de clics, toques o entradas del teclado.
inputDelay* El retraso de entrada de la interacción.
processingDuration* Es el tiempo desde que se comenzó a ejecutar el primer objeto de escucha de eventos en respuesta a la interacción del usuario hasta que se completó todo el procesamiento del objeto de escucha de eventos.
presentationDelay* El retraso de presentación de la interacción, que se produce desde que terminan los controladores de eventos hasta que se pinta el siguiente fotograma.
longAnimationFrameEntries* Entradas del LoAF asociadas con la interacción. Consulta la siguiente sección para obtener más información.
*Novedades de la versión 4

A partir de la versión 4 de la biblioteca de 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 fase de INP (demora de entrada, duración de procesamiento y demora de presentación) y la API de Long Animation Frames (LoAF).

La API de Long Animation Frames (LoAF)

Navegadores compatibles

  • Chrome: 123
  • Edge: 123.
  • Firefox: No es compatible.
  • Safari: no es compatible.

Origen

La depuración de interacciones con datos de campo es una tarea desafiante. 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 Datos
duration Es la duración del fotograma de animación largo, hasta que finaliza 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 largas que ejecutan JavaScript, así como cualquier tarea de renderización larga posterior en el fotograma.
firstUIEventTimestamp Es la marca de tiempo del momento en que el evento se puso en cola durante la trama. Es útil para determinar el inicio del retraso de entrada de una interacción.
startTime Es la marca de tiempo de inicio del fotograma.
renderStart Cuándo comenzó el trabajo de renderización del fotograma Esto incluye cualquier devolución de llamada de requestAnimationFrame (y de ResizeObserver, si corresponde), pero posiblemente antes de que comience cualquier trabajo de diseño o estilo.
styleAndLayoutStart Cuando se aplica el estilo o el 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 Es un array de elementos que contiene información de atribución de la secuencia de comandos que contribuye a la INP de la página.
Visualización de un fotograma de animación largo según el modelo LoAF.
Un diagrama de los tiempos de un fotograma de animación largo según la API de LoAF (menos blockingDuration).

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 del objeto de atribución de la secuencia de comandos Datos
invoker El invocador. Esto puede variar según el tipo de llamador 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 llamador. 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 fotograma de animación largo.
sourceCharPosition La posición del carácter en la secuencia de comandos que sourceURL identifica.
sourceFunctionName Es el nombre de la función en la secuencia de comandos identificada.

Cada entrada de este array contiene los datos que se muestran en esta tabla, que te brinda información sobre la secuencia de comandos que fue responsable de la interacción lenta y cómo lo fue.

Mide e identifica las causas comunes de las interacciones lentas

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

Duración del procesamiento prolongada

La duración del procesamiento de una interacción es el tiempo que tardan en ejecutarse por completo las devoluciones de llamada del controlador de eventos registradas de la interacción y cualquier otra cosa que pueda suceder entre ellas. La biblioteca de Web Vitals muestra duraciones de procesamiento altas:

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 confirmes que este es el problema, puedes profundizar con 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 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 que realices el trabajo de averiguar exactamente qué interacción o cuál de sus controladores de eventos fue responsable de los valores altos de duración de procesamiento. Además, como las secuencias de comandos de terceros suelen registrar sus propios controladores de eventos, puedes determinar si tu código fue el responsable. En el caso del código sobre el que tienes control, te recomendamos que optimices las 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 tener en cuenta. 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 de 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 observas que algunas interacciones tienen demoras de entrada altas, deberás averiguar qué estaba sucediendo en la página en el momento de la interacción que causó la demora de entrada prolongada, y eso 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 cuando 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 mucho JavaScript pueden iniciar el 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 ves demoras de entrada altas y tipos de invocador de 'classic-script' o 'module-script', es justo decir que las secuencias de comandos de tu sitio tardan mucho tiempo en evaluarse y bloquean el subproceso principal el tiempo suficiente para retrasar las interacciones. Para reducir este tiempo de bloqueo, divide tus secuencias de comandos en paquetes más pequeños, aplaza el código que no se use inicialmente para que se cargue más adelante y audita tu sitio en busca de código que no se use y que puedas quitar por completo.

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

Si bien las demoras de entrada suelen ocurrir mientras se carga una página, es igual de probable que ocurran después de que se cargue una página, debido a una causa completamente diferente. Las causas comunes de las demoras de entrada después de la carga de la página pueden ser el código que se ejecuta periódicamente debido a una llamada setInterval anterior o incluso las devoluciones de llamada de eventos que se pusieron 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 en el caso de la solución de problemas de valores de duración de procesamiento elevado, los retrasos en las entradas elevados debido a las causas mencionadas anteriormente te proporcionarán datos detallados de atribución de secuencias de comandos. Sin embargo, lo que es diferente es que el tipo de llamador cambiará según la naturaleza del trabajo que retrasó la interacción:

  • 'user-callback' indica que la tarea de bloqueo provino de setInterval, setTimeout o incluso requestAnimationFrame.
  • 'event-listener' indica que la tarea de bloqueo proviene 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 provino de algún 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 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 prolongadas en la presentación

Las demoras de 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 el que se pintó el siguiente fotograma. Ocurren 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 de Web Vitals puede indicarte cuánto tiempo duró el retraso de presentación de 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 de 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 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 que se vuelva a calcular el estilo y se realice el trabajo de diseño.

Estas devoluciones de llamada pueden tardar bastante tiempo en completarse si el trabajo que se realiza en ellas es complejo. Si sospechas que los valores altos de retraso de presentación se deben al trabajo que realizas con requestAnimationFrame, puedes usar los datos de LoAF que muestra 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 ves que una parte significativa del tiempo de demora de la presentación se dedica a una devolución de llamada de requestAnimationFrame, asegúrate de que el trabajo que realizas en estas devoluciones de llamada se limite a realizar tareas que generen una actualización real de la interfaz de usuario. Cualquier otro trabajo que no toque el DOM ni 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.