API de Long Animation Frames

La API de Long Animation Frames (LoAF, que se pronuncia Lo-Af) es una actualización de la API de Long Tasks para proporcionar una mejor comprensión de las actualizaciones lentas de la interfaz de usuario (IU). Esto puede ser útil para identificar fotogramas de animación lentos que probablemente afecten la métrica de interacción a la siguiente pintura (INP) de las métricas web esenciales, que mide la capacidad de respuesta, o para identificar otros bloqueos de la IU que afectan la fluidez.

Estado de la API

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: not supported.
  • Safari: not supported.

Source

Después de una prueba de origen de Chrome 116 a Chrome 122, la API de LoAF se lanzó desde Chrome 123.

En segundo plano: la API de Long Tasks

Browser Support

  • Chrome: 58.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

La API de Long Animation Frames es una alternativa a la API de Long Tasks, que está disponible en Chrome desde hace algún tiempo (desde Chrome 58). Como su nombre lo indica, la API de Long Task te permite supervisar tareas largas, que son tareas que ocupan el subproceso principal durante 50 milisegundos o más. Las tareas largas se pueden supervisar con la interfaz PerformanceLongTaskTiming, con un PeformanceObserver:

const observer = new PerformanceObserver((list) => {
  console.log(list.getEntries());
});

observer.observe({ type: 'longtask', buffered: true });

Es probable que las tareas largas causen problemas de capacidad de respuesta. Si un usuario intenta interactuar con una página (por ejemplo, hacer clic en un botón o abrir un menú), pero el subproceso principal ya está realizando una tarea larga, la interacción del usuario se retrasa hasta que se complete esa tarea.

Para mejorar la capacidad de respuesta, a menudo se recomienda dividir las tareas largas. Si cada tarea larga se divide en una serie de varias tareas más pequeñas, es posible que se ejecuten tareas más importantes entre ellas para evitar demoras significativas en la respuesta a las interacciones.

Por lo tanto, cuando se intenta mejorar la capacidad de respuesta, el primer esfuerzo suele ser ejecutar un seguimiento de rendimiento y observar las tareas largas. Esto puede ser a través de una herramienta de auditoría basada en el laboratorio, como Lighthouse (que tiene una auditoría Evitar tareas largas en el subproceso principal), o analizando las tareas largas en las Herramientas para desarrolladores de Chrome.

Las pruebas basadas en laboratorios a menudo son un mal punto de partida para identificar problemas de capacidad de respuesta, ya que es posible que estas herramientas no incluyan interacciones. Cuando lo hacen, se trata de un subconjunto pequeño de interacciones probables. Lo ideal sería que midieras las causas de las interacciones lentas en el campo.

Desventajas de la API de Long Tasks

Medir tareas largas en el campo con un observador de rendimiento solo es útil en cierta medida. En realidad, no proporciona mucha información más allá del hecho de que se realizó una tarea larga y cuánto tiempo tardó.

Las herramientas de supervisión de usuarios reales (RUM) suelen usar esto para determinar la tendencia de la cantidad o la duración de las tareas largas, o para identificar en qué páginas ocurren. Sin embargo, sin los detalles subyacentes de lo que causó la tarea larga, esto solo tiene un uso limitado. La API de Long Tasks solo tiene un modelo de atribución básico, que, en el mejor de los casos, solo te indica el contenedor en el que se produjo la tarea larga (el documento de nivel superior o un <iframe>), pero no la secuencia de comandos o la función que la llamó, como se muestra en una entrada típica:

{
  "name": "unknown",
  "entryType": "longtask",
  "startTime": 31.799999997019768,
  "duration": 136,
  "attribution": [
    {
      "name": "unknown",
      "entryType": "taskattribution",
      "startTime": 0,
      "duration": 0,
      "containerType": "window",
      "containerSrc": "",
      "containerId": "",
      "containerName": ""
    }
  ]
}

La API de Long Tasks también es una vista incompleta, ya que también puede excluir algunas tareas importantes. Algunas actualizaciones, como la renderización, se producen en tareas independientes que, idealmente, deberían incluirse junto con la ejecución anterior que causó esa actualización para medir con precisión el "trabajo total" de esa interacción. Para obtener más detalles sobre las limitaciones de depender de las tareas, consulta la sección "Dónde fallan las tareas largas" de la explicación.

El último problema es que la medición de tareas largas solo informa sobre tareas individuales que tardan más que el límite de 50 milisegundos. Un fotograma de animación puede estar compuesto por varias tareas más pequeñas que este límite de 50 milisegundos, pero, en conjunto, aún bloquean la capacidad de renderización del navegador.

La API de Long Animation Frames

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: not supported.
  • Safari: not supported.

Source

La API de Long Animation Frames (LoAF) es una API nueva que busca abordar algunas de las deficiencias de la API de Long Tasks para permitir que los desarrolladores obtengan estadísticas más prácticas que ayuden a abordar los problemas de capacidad de respuesta y mejorar la INP, así como obtener estadísticas sobre los problemas de fluidez.

Una buena capacidad de respuesta significa que una página responde rápidamente a las interacciones que se realizan con ella. Esto implica poder pintar las actualizaciones que el usuario necesita de forma oportuna y evitar que se bloqueen estas actualizaciones. Para los INP, se recomienda responder en 200 milisegundos o menos, pero para otras actualizaciones (por ejemplo, animaciones), incluso 200 milisegundos pueden ser demasiado largos.

La API de Long Animation Frames es un enfoque alternativo para medir el trabajo de bloqueo. En lugar de medir las tareas individuales, la API de Long Animation Frames, como su nombre lo sugiere, mide los marcos de animación largos. Un fotograma de animación largo se produce cuando una actualización de renderización se retrasa más de 50 milisegundos (el mismo umbral que el de la API de Long Tasks).

Los fotogramas de animación largos se miden desde el inicio de las tareas que requieren una renderización. Cuando la primera tarea en un posible fotograma de animación larga no requiere una renderización, el fotograma de animación larga finaliza cuando se completa la tarea sin renderización y se inicia un nuevo fotograma de animación larga potencial con la siguiente tarea. Estos fotogramas de animación larga que no se renderizan aún se incluyen en la API de Long Animation Frames cuando son superiores a 50 milisegundos (con un tiempo de renderStart de 0) para permitir la medición del trabajo potencialmente bloqueador.

Los fotogramas de animación largos se pueden observar de manera similar a las tareas largas con un PerformanceObserver, pero en su lugar, se observa el tipo long-animation-frame:

const observer = new PerformanceObserver((list) => {
  console.log(list.getEntries());
});

observer.observe({ type: 'long-animation-frame', buffered: true });

Los fotogramas de animación largos anteriores también se pueden consultar desde el Cronograma de rendimiento de la siguiente manera:

const loafs = performance.getEntriesByType('long-animation-frame');

Sin embargo, hay un maxBufferSize para las entradas de rendimiento después del cual se descartan las entradas más recientes, por lo que el enfoque de PerformanceObserver es el recomendado. El tamaño del búfer de long-animation-frame se establece en 200, al igual que para long-tasks.

Ventajas de observar marcos en lugar de tareas

La ventaja clave de observar esto desde una perspectiva de fotogramas en lugar de una perspectiva de tareas es que una animación larga puede estar compuesta por cualquier cantidad de tareas que, de forma acumulativa, generen un fotograma de animación largo. Esto aborda el último punto mencionado anteriormente, en el que es posible que la API de Long Tasks no muestre la suma de muchas tareas más pequeñas que bloquean la renderización antes de un fotograma de animación.

Otra ventaja de esta vista alternativa en tareas largas es la capacidad de proporcionar desgloses de tiempos de todo el fotograma. En lugar de incluir solo un startTime y un duration, como la API de Long Tasks, LoAF incluye un desglose mucho más detallado de las distintas partes de la duración del fotograma.

Marcas de tiempo y duraciones de fotogramas

  • startTime: Es la hora de inicio del fotograma de animación largo en relación con la hora de inicio de la navegación.
  • duration: Es la duración del fotograma de animación largo (sin incluir el tiempo de presentación).
  • renderStart: Es la hora de inicio del ciclo de renderización, que incluye devoluciones de llamada de requestAnimationFrame, cálculo de diseño y estilo, y devoluciones de llamada de observador de intersección y observador de cambio de tamaño.
  • styleAndLayoutStart: Es el comienzo del período dedicado a los cálculos de diseño y estilo.
  • firstUIEventTimestamp: Es la hora del primer evento de IU (mouse, teclado, etcétera) que se procesará durante el transcurso de este fotograma. Ten en cuenta que firstUIEventTimestamp puede estar en un fotograma anterior si hubo una demora entre el momento en que ocurrió el evento y el momento en que se procesó.
  • blockingDuration: Es la duración total en milisegundos durante la cual el fotograma de animación bloquearía el procesamiento de la entrada o de otras tareas de alta prioridad.

Una explicación de blockingDuration

Un fotograma de animación largo puede estar compuesto por varias tareas. blockingDuration es la suma de las duraciones de las tareas superiores a 50 milisegundos (incluida la duración de renderización final dentro de la tarea más larga).

Por ejemplo, si un fotograma de animación largo estuviera compuesto por dos tareas de 55 milisegundos y 65 milisegundos, seguidas de una renderización de 20 milisegundos, el duration sería de aproximadamente 140 milisegundos con un blockingDuration de (55 - 50) + (65 + 20 - 50) = 40 milisegundos. Durante 40 milisegundos de este fotograma de animación de 140 milisegundos de duración, se consideró que el fotograma estaba bloqueado para controlar la entrada.

Si se debe mirar duration o blockingDuration

En el caso de la pantalla común de 60 hercios, un navegador intentará programar un fotograma al menos cada 16.66 milisegundos (para garantizar actualizaciones fluidas) o después de una tarea de alta prioridad, como el manejo de entradas (para garantizar actualizaciones responsivas). Sin embargo, si no hay entradas ni otras tareas de alta prioridad, pero hay una cola de otras tareas, el navegador suele continuar con el fotograma actual mucho más allá de los 16.66 milisegundos, sin importar qué tan bien estén divididas las tareas en él. Es decir, el navegador siempre intentará priorizar las entradas, pero puede optar por abordar una cola de tareas en lugar de actualizaciones de renderización. Esto se debe a que la renderización es un proceso costoso, por lo que procesar una tarea de renderización combinada para varias tareas suele generar una reducción general del trabajo.

Por lo tanto, los fotogramas de animación largos con un blockingDuration bajo o nulo deberían seguir siendo responsivos a la entrada. Por lo tanto, reducir o eliminar blockingDuration dividiendo las tareas largas es clave para mejorar la capacidad de respuesta, medida por el INP.

Sin embargo, muchos fotogramas de animación largos, independientemente de blockingDuration, indican que las actualizaciones de la IU están retrasadas y, por lo tanto, pueden afectar la fluidez y generar la sensación de una interfaz de usuario con retrasos para el desplazamiento o las animaciones, incluso si estos son menos un problema para la capacidad de respuesta, como se mide con INP. Para comprender los problemas en esta área, observa duration, pero estos pueden ser más difíciles de optimizar, ya que no puedes resolverlos dividiendo el trabajo, sino que debes reducirlo.

Tiempos de fotogramas

Las marcas de tiempo mencionadas anteriormente permiten que el fotograma de animación largo se divida en tiempos:

Tiempos Cálculo
Hora de inicio startTime
Hora de finalización startTime + duration
Duración del trabajo renderStart ? renderStart - startTime : duration
Duración del procesamiento renderStart ? (startTime + duration) - renderStart: 0
Renderización: Duración previa al diseño styleAndLayoutStart ? styleAndLayoutStart - renderStart : 0
Renderización: Duración del estilo y el diseño styleAndLayoutStart ? (startTime + duration) - styleAndLayoutStart : 0

Mejor atribución de secuencias de comandos

El tipo de entrada long-animation-frame incluye mejores datos de atribución de cada secuencia de comandos que contribuyó a un fotograma de animación largo (para secuencias de comandos de más de 5 milisegundos).

Al igual que con la API de Long Tasks, se proporcionará en un array de entradas de atribución, cada una de las cuales detalla lo siguiente:

  • Un name y un EntryType mostrarán script.
  • Un invoker significativo que indica cómo se llamó a la secuencia de comandos (por ejemplo, 'IMG#id.onload', 'Window.requestAnimationFrame' o 'Response.json.then').
  • El invokerType del punto de entrada de la secuencia de comandos:
    • user-callback: Es una devolución de llamada conocida registrada desde una API de plataforma web (por ejemplo, setTimeout, requestAnimationFrame).
    • event-listener: Es un objeto de escucha de un evento de la plataforma (por ejemplo, click, load, keyup).
    • resolve-promise: Controlador de una promesa de plataforma (por ejemplo, fetch()). Ten en cuenta que, en el caso de las promesas, todos los controladores de las mismas promesas se mezclan como una "secuencia de comandos")..
    • reject-promise: Es similar a resolve-promise, pero para el rechazo.
    • classic-script: Evaluación de la secuencia de comandos (por ejemplo, <script> o import())
    • module-script: Igual que classic-script, pero para secuencias de comandos de módulos.
  • Datos de tiempo separados para esa secuencia de comandos:
    • startTime: Es la hora en la que se invocó la función de entrada.
    • duration: Es la duración entre startTime y el momento en que se termina de procesar la cola de microtareas posterior.
    • executionStart: Es la hora posterior a la compilación.
    • forcedStyleAndLayoutDuration: Es el tiempo total dedicado a procesar el diseño y el estilo forzados dentro de esta función (consulta fragmentación).
    • pauseDuration: Es el tiempo total dedicado a "pausar" operaciones síncronas (alerta, XHR síncrona).
  • Detalles de la fuente de la secuencia de comandos:
    • sourceURL: Es el nombre del recurso de la secuencia de comandos cuando está disponible (o está vacío si no se encuentra).
    • sourceFunctionName: Es el nombre de la función de la secuencia de comandos cuando está disponible (o está vacío si no se encuentra).
    • sourceCharPosition: Es la posición del carácter de la secuencia de comandos cuando está disponible (o -1 si no se encuentra).
  • windowAttribution: Es el contenedor (el documento de nivel superior o un <iframe>) en el que se produjo el fotograma de animación largo.
  • window: Es una referencia a la ventana del mismo origen.

Cuando se proporcionan, las entradas de origen permiten que los desarrolladores sepan exactamente cómo se llamó a cada secuencia de comandos en el fotograma de animación largo, hasta la posición del carácter en la secuencia de comandos de llamada. Esto proporciona la ubicación exacta en un recurso de JavaScript que generó el fotograma de animación largo.

Ejemplo de una entrada de rendimiento de long-animation-frame

Un ejemplo completo de entrada de rendimiento de long-animation-frame, que contiene una sola secuencia de comandos, es el siguiente:

{
  "blockingDuration": 0,
  "duration": 60,
  "entryType": "long-animation-frame",
  "firstUIEventTimestamp": 11801.099999999627,
  "name": "long-animation-frame",
  "renderStart": 11858.800000000745,
  "scripts": [
    {
      "duration": 45,
      "entryType": "script",
      "executionStart": 11803.199999999255,
      "forcedStyleAndLayoutDuration": 0,
      "invoker": "DOMWindow.onclick",
      "invokerType": "event-listener",
      "name": "script",
      "pauseDuration": 0,
      "sourceURL": "https://web.dev/js/index-ffde4443.js",
      "sourceFunctionName": "myClickHandler",
      "sourceCharPosition": 17796,
      "startTime": 11803.199999999255,
      "window": [Window object],
      "windowAttribution": "self"
    }
  ],
  "startTime": 11802.400000000373,
  "styleAndLayoutStart": 11858.800000000745
}

Como se puede ver, esto proporciona una cantidad sin precedentes de datos para que los sitios web puedan comprender la causa de las actualizaciones de renderización con retraso.

Usa la API de Long Animation Frames en el campo

Herramientas como las Herramientas para desarrolladores de Chrome y Lighthouse, si bien son útiles para descubrir y reproducir problemas, son herramientas de laboratorio que pueden pasar por alto aspectos importantes de la experiencia del usuario que solo los datos de campo pueden proporcionar.

La API de Long Animation Frames está diseñada para usarse en el campo y recopilar datos contextuales importantes para las interacciones del usuario que la API de Long Tasks no podía. Esto puede ayudarte a identificar y reproducir problemas de interactividad que, de otro modo, podrías no haber descubierto.

Compatibilidad con la API de Long Animation Frames para detectar funciones

Puedes usar el siguiente código para probar si la API es compatible:

if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
  // Monitor LoAFs
}

El caso de uso más obvio de la API de Long Animation Frames es ayudar a diagnosticar y corregir problemas de Interaction to Next Paint (INP), y esa fue una de las razones clave por las que el equipo de Chrome desarrolló esta API. Una buena INP es aquella en la que se responde a todas las interacciones en 200 milisegundos o menos desde la interacción hasta que se pinta el fotograma. Dado que la API de Long Animation Frames mide todos los fotogramas que tardan 50 ms o más, la mayoría de las INP problemáticas deben incluir datos de LoAF para ayudarte a diagnosticar esas interacciones.

El "LoAF de INP" es el LoAF que incluye la interacción de INP, como se muestra en el siguiente diagrama:

Ejemplos de fotogramas de animación largos en una página, con el LoAF de INP destacado.
Una página puede tener muchos LoAF, uno de los cuales está relacionado con la interacción de INP.

En algunos casos, es posible que un evento de INP abarque dos LoAF, por lo general, si la interacción se produce después de que el fotograma inició la parte de renderización del fotograma anterior, por lo que el controlador de eventos se procesa en el siguiente fotograma:

Ejemplos de fotogramas de animación largos en una página, con los LoAF de INP destacados.
Una página puede tener muchos LoAF, uno o más de los cuales pueden estar relacionados con la interacción de INP.

Incluso es posible que abarque más de dos LoAF en algunas circunstancias excepcionales.

Si registras los datos de LoAF asociados con la interacción de INP, puedes obtener mucha más información sobre la interacción de INP para ayudar a diagnosticarla. Esto es especialmente útil para comprender el retardo de entrada, ya que puedes ver qué otras secuencias de comandos se estaban ejecutando en ese fotograma.

También puede ser útil comprender la duración del procesamiento y la demora en la presentación inexplicables si tus controladores de eventos no reproducen los valores que se ven para ellos, ya que es posible que se estén ejecutando otras secuencias de comandos para tus usuarios que no se incluyen en tus propias pruebas.

No hay una API directa para vincular una entrada de INP con sus entradas de LoAF relacionadas, aunque es posible hacerlo en el código comparando las horas de inicio y finalización de cada una (consulta la WhyNp). La biblioteca web-vitals incluye todos los LoAF que se cruzan en la propiedad longAnimationFramesEntries de la interfaz de atribución de INP a partir de la versión 4.

Una vez que hayas vinculado las entradas de LoAF, podrás incluir información con la atribución de INP. El objeto scripts contiene algunos de los datos más valiosos, ya que puede mostrar qué más se estaba ejecutando en esos fotogramas, por lo que enviar esos datos a tu servicio de estadísticas te permitirá comprender mejor por qué las interacciones fueron lentas.

Informar los LoAF para la interacción de INP es una buena manera de encontrar los problemas de interactividad más urgentes en tu página. Cada usuario puede interactuar de forma diferente con tu página y, con un volumen suficiente de datos de atribución de INP, se incluirán algunos problemas potenciales en los datos de atribución de INP. Esto te permite ordenar las secuencias de comandos por volumen para ver cuáles se correlacionan con una INP lenta.

Cómo informar más datos de animación de larga duración a un extremo de estadísticas

Una desventaja de solo mirar los LoAF de INP es que podrías pasar por alto otras áreas potenciales de mejora que podrían causar problemas de INP en el futuro. Esto puede generar la sensación de que estás dando vueltas en círculos, ya que corriges un problema de INP con la expectativa de ver una gran mejora, solo para descubrir que la siguiente interacción más lenta es solo un poco mejor que esa, por lo que tu INP no mejora mucho.

Por lo tanto, en lugar de solo mirar el LoAF de INP, te recomendamos que consideres todos los LoAF durante el ciclo de vida de la página: