Aprende a encontrar interacciones lentas en los datos de campo de tu sitio web para que puedas descubrir oportunidades de mejorar su Interaction to Next Paint.
Los datos de campo son datos que te indican cómo los usuarios reales experimentan tu sitio web. Permite detectar problemas que no se pueden encontrar solo en los datos de lab. En lo que respecta a la Interaction to Next Paint (INP), los datos de campo son esenciales para identificar las interacciones lentas y proporcionan pistas vitales para ayudarte a corregirlas.
En esta guía, aprenderás a evaluar rápidamente la INP de tu sitio web con datos de campo del Informe sobre la experiencia del usuario de Chrome (CrUX) para ver si tu sitio web tiene problemas con la INP. Posteriormente, 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 de la API de Long Animation Frames (LoAF) para recopilar e interpretar datos de campo de las interacciones lentas en tu sitio web.
Comienza con CrUX para evaluar la 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. El CrUX puede proporcionar datos sobre la INP y otras Core Web Vitals 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áginas de detalles del producto y de fichas de productos. 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, se mostrarán los datos de campo correspondientes, si están disponibles, para varias métricas, incluida la INP. También puedes usar los botones de activación para verificar tus valores de INP en las dimensiones de dispositivos móviles y computadoras de escritorio.
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. 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 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 de web-vitals
La biblioteca de JavaScript 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 usarla para registrar varias métricas, incluida la INP en los navegadores que la admiten.
La compilación estándar de la biblioteca de web-vitals se puede usar para obtener datos básicos de 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, deberás enviarlos 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 te diría el CrUX. Aquí es donde entra en juego la compilación de atribución de la biblioteca web-vitals.
Ve más allá 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 en el campo para ayudarte a solucionar mejor los problemas de interacciones que afectan la INP de tu sitio web. Se puede acceder a estos datos a través del objeto attribution que se muestra 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
});
Además de la 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?"
- "¿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 de atribución básicos que puedes obtener de la biblioteca y que pueden ayudarte a descubrir algunas causas de alto nivel de las interacciones lentas en tu sitio web:
Clave de 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 por clics, presiones o entradas de teclado. |
inputDelay*
|
El retraso de entrada de la interacción |
processingDuration*
|
Es 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 finalizó todo el procesamiento del objeto de escucha de eventos. |
presentationDelay*
|
La demora de presentación de la interacción, que se produce desde que finalizan los controladores de eventos hasta que se renderiza el siguiente fotograma. |
longAnimationFrameEntries*
|
Son las entradas del LoAF asociadas con la interacción. Consulta la siguiente sección para obtener más información. |
A partir de la versión 4 de la biblioteca web-vitals, puedes obtener información aún más detallada sobre las interacciones problemáticas a través de los datos que proporciona con los desgloses de las fases de INP (retraso de entrada, duración del procesamiento y retraso de presentación) y la API de Long Animation Frames (LoAF).
API de Long Animation Frames (LoAF)
Depurar interacciones con datos de campo es una tarea difícil. Sin embargo, con los datos de 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 las 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 de web-vitals expone un array de entradas de LoAF en la clave longAnimationFrameEntries del objeto attribution. En la siguiente tabla, se enumeran algunos datos clave que puedes encontrar en cada entrada de LoAF:
| Clave del 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 en el que el navegador no pudo responder con rapidez 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 se puso en cola el evento durante el fotograma. Es útil para determinar el inicio de la demora de entrada de una interacción. |
startTime
|
Es la marca de tiempo de inicio del fotograma. |
renderStart
|
Indica 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 estilo o diseño.
|
styleAndLayoutStart
|
Cuándo se produce el trabajo de estilo o diseño en el fotograma. Puede ser útil para determinar la duración del trabajo de estilo o diseño cuando se consideran otras marcas de tiempo disponibles. |
scripts
|
Un array de elementos que contienen información de atribución de secuencias de comandos que contribuyen al INP de la página. |
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 de objeto de atribución de secuencia de comandos | Datos |
|---|---|
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 de 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 en la que se originó el fotograma de animación largo. |
sourceCharPosition
|
La posición del carácter en la secuencia de comandos identificada por sourceURL.
|
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 causó la interacción lenta y cómo lo hizo.
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, se explicará cómo puedes usar los datos de LoAF que se muestran en la biblioteca de web-vitals para determinar algunas causas de las interacciones lentas.
Duraciones de procesamiento largas
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 registrado de la interacción y cualquier otra cosa que pueda suceder 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 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 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.toSorted((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 precisa detrás de una interacción con valores de duración de procesamiento altos, incluidos los siguientes:
- El elemento y su objeto de escucha de eventos registrado.
- El archivo de secuencia de comandos y la posición del carácter 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 necesitas hacer 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 del 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 o no. En el caso del código que controlas, te recomendamos que optimices las tareas largas.
Retrasos de entrada prolongados
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 ocurre antes de la duración del procesamiento, lo que se conoce como retraso de entrada. Este es el tiempo que transcurre desde que el usuario inicia la interacción hasta el momento en que comienzan a ejecutarse las devoluciones de llamada del controlador de eventos, y ocurre 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 de una interacción:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
});
Si notas que algunas interacciones tienen retrasos de entrada altos, deberás averiguar qué estaba sucediendo en la página en el momento de la interacción que causó el retraso de entrada prolongado, y eso suele reducirse a si la interacción se produjo 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, se ponen en cola y se procesan todo tipo de tareas. Si el usuario intenta interactuar con la página mientras se realiza todo este trabajo, la interacción puede retrasarse. Las páginas que cargan mucho código JavaScript pueden iniciar el trabajo de compilar y evaluar secuencias de comandos, así como ejecutar funciones que preparan una página para las interacciones de los usuarios. Este trabajo puede interponerse si el usuario interactúa mientras se produce 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.toSorted((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 retrasos de entrada altos 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. Puedes reducir este tiempo de bloqueo dividiendo tus secuencias de comandos en paquetes más pequeños, aplazando el código que no se usa inicialmente para que se cargue más adelante y auditando tu sitio para detectar el código que no se usa y que puedes quitar por completo.
¿Fue después de la carga de la página?
Si bien los retrasos en la entrada suelen ocurrir mientras se carga una página, también es posible que ocurran después de que se cargue una página, debido a una causa completamente diferente. Las causas comunes de los retrasos de entrada después de la carga de la página pueden ser el código que se ejecuta de forma periódica debido a una llamada anterior a setInterval o, incluso, las devoluciones de llamada de eventos que se pusieron 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.toSorted((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'
}
});
Al igual que con la solución de problemas de valores altos de duración del procesamiento, los retrasos de entrada altos 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 invocador cambiará según la naturaleza del trabajo que retrasó la interacción:
'user-callback'indica que la tarea de bloqueo era desetInterval,setTimeouto inclusorequestAnimationFrame.'event-listener'indica que la tarea de bloqueo provenía de una entrada anterior que se puso en cola y aún se está procesando.'resolve-promise'y'reject-promise'significa 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 por dónde empezar a buscar y si el retraso de entrada se debió a tu propio código o al de una secuencia de comandos de terceros.
Retrasos prolongados en la presentación
Los retrasos en la presentación son el último tramo de una interacción y comienzan cuando finalizan los controladores de eventos de la interacción, hasta el punto en el que se renderizó 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 las demoras de entrada, la biblioteca de web-vitals puede indicarte cuánto duró la demora 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 retrasos de presentación altos para las interacciones que contribuyen al INP de tu sitio web, los culpables pueden variar, pero aquí hay un par de causas que debes tener en cuenta.
Trabajo costoso de estilo y diseño
Los retrasos prolongados en la presentación pueden ser un trabajo costoso de recalculación de estilo y diseño que surge de varias causas, incluidos los selectores CSS complejos y los tamaños de DOM grandes. Puedes medir la duración de este trabajo con los tiempos de LoAF que se muestran 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.toSorted((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 estilo y diseño de un fotograma, pero sí 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 trabajo de estilo y diseño.
Devoluciones de llamada de requestAnimationFrame de larga duración
Una posible causa de los retrasos prolongados 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 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 un tiempo considerable 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 estás realizando 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.toSorted((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 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 que puedes usar 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), puedes tener más confianza en las interacciones más problemáticas y, luego, pasar a reproducir las interacciones problemáticas en el lab y, luego, corregirlas.