The Long Animation Frames API (LoAF) for Diagnosing Main-Thread Blocking
AI generated
60fps
ms
Web Performance / Browser APIs
The Long Animation Frames API (LoAF) for Diagnosing Main-Thread Blocking
Finally seeing whether rendering or script is delaying the interaction

The Long Animation Frames API delivers a far more granular breakdown of blocking frames than the older Long Tasks API, showing exactly how much of each long frame comes from script execution versus rendering. That makes it a central tool for precisely diagnosing INP regressions.

14 min read Long Animation Frames API INP Debugging

1. A Recap: The Long Tasks API and Its Limits

The Long Tasks API reports, via a PerformanceObserver, every main-thread task that runs longer than fifty milliseconds, providing a start and end time along with a rough attribution of which context, such as a specific iframe, the task came from. For years this was the only standardized tool that made long blocking tasks visible at all, without relying on manual profiling in the DevTools.

The decisive weakness of the Long Tasks API, though, is its lack of internal breakdown: it reports that a task took a long time, but not how much of that came from JavaScript execution, how much from style recalculation, and how much from layout. For targeted troubleshooting of interaction delays that was often too coarse, which is why developers frequently still needed manual profiling in the DevTools Performance tab.

2. What is the Long Animation Frames API

The Long Animation Frames API, LoAF for short, addresses exactly this weakness by providing a detailed breakdown of the individual phases for every blocking animation frame of fifty milliseconds or more. It's registered through a PerformanceObserver with the entry type long-animation-frame, where each entry carries fields such as renderStart, styleAndLayoutStart, and a total duration for style and layout computation.

Additionally, every LoAF entry provides an array called scripts containing individual PerformanceScriptTiming objects, which for every script executed within the frame include details such as the invoker, source file, function location, and individual execution duration. For the first time, this makes it possible to pinpoint exactly which script or event handler was responsible for a given delay, without manual profiling.

3. Code Example: Setting Up a PerformanceObserver for LoAF

The setup follows the familiar pattern of the Performance Observer APIs: you register an observer for the entry type long-animation-frame and process the reported entries in the callback function. The scripts array is particularly valuable here, since it allows attribution down to individual function calls instead of just knowing the total duration of the frame.

The example below determines the most expensive script for every detected long frame and logs it together with its source, which in practice very quickly points to the actual culprits behind interaction delays.


const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    const totalDuration = entry.duration;
    const renderPortion = entry.styleAndLayoutStart
      ? entry.duration - (entry.styleAndLayoutStart - entry.startTime)
      : 0;

    // Find the most expensive script within the frame
    const worstScript = entry.scripts
      .sort((a, b) => b.duration - a.duration)[0];

    console.warn('Long animation frame detected', {
      durationMs: totalDuration,
      renderingPortionMs: renderPortion,
      source: worstScript?.sourceURL,
      functionName: worstScript?.sourceFunctionName,
      invoker: worstScript?.invoker,
    });
  }
});

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

4. Breaking Down the Frame Phases in Detail

A LoAF entry decomposes a blocking frame into clearly defined time segments: the time until script execution starts, the script execution itself with all involved function calls, the subsequent style and layout computation, and finally the time until the actual rendering hits the screen. Each of these phases is available as its own timestamp or duration within the entry.

Particularly useful is the forcedStyleAndLayoutDuration field within the individual script entries, which shows how much time was lost to forced synchronous layout calculations, typically caused when a script queries a layout property like offsetHeight right after a DOM change. This so-called layout thrashing was essentially invisible with the Long Tasks API and can now be pinpointed directly with LoAF.

5. Correctly Diagnosing Rendering vs. Script Share

If the script portion of a long frame dominates, that usually points to inefficient JavaScript logic, such as an overly complex calculation inside an event handler, or a third-party library doing too much work synchronously. In that case the scripts array helps directly, since it names the exact source and function that should be optimized or split into smaller chunks.

If, on the other hand, the style and layout portion dominates, the cause is usually extensive DOM changes, complex CSS selectors, or a forced reflow from interleaved reads and writes of layout properties. Here the precise timestamp analysis helps determine whether the problem genuinely lies in the rendering itself, or in a script that unnecessarily forces that rendering early.

6. A Practical Debugging Example for an INP Regression

Suppose real-user monitoring shows a noticeable jump in Interaction to Next Paint after a release, specifically for the click on the add-to-cart button. The first step is to use the Event Timing API to find the exact time window of the affected interaction, and then filter all LoAF entries whose time window overlaps with it.

In practice, this often reveals that a newly added analytics library synchronously serializes a large JSON object on click, which shows up in the scripts array as the dominant entry with a high duration, while the rendering portion of the frame barely increased. Without LoAF, the Long Tasks API would only have reported a longer overall task without naming the responsible library, which would have made troubleshooting take much longer.

7. Integration Into Real-User Monitoring and web-vitals

For production use, it pays off to send LoAF data not just to the local console but aggregated to a real-user monitoring system, in order to spot systematic patterns across many user sessions. Google's web-vitals library already offers experimental attribution extensions that automatically tie LoAF data to INP measurements.

When aggregating, it's important not to transmit every single LoAF entry with full detail, since that quickly generates large volumes of data, but instead to selectively collect only the entries that actually overlap in time with a poorly measured interaction. That keeps the data volume manageable while still preserving enough context for later troubleshooting.

8. Browser Support for the Long Animation Frames API

The Long Animation Frames API is currently supported by Chrome and other Chromium-based browsers, while Firefox and Safari haven't implemented it yet. Since this is a pure diagnostic API with no effect on the user experience, a feature test is straightforward, and the lack of support elsewhere poses no functional risk.

For projects measuring users across different browsers, falling back to the still-available Long Tasks API in non-supporting browsers is a sensible approach, even though it naturally provides less detailed data. That way, at least a coarse detection of blocking tasks remains available across all browsers.

9. Conclusion

The Long Animation Frames API closes a long-standing gap between the coarse signal of the Long Tasks API and the labor-intensive manual profiling in the DevTools, delivering, automatically and usable in production, exactly the information needed for INP debugging. Anyone serious about analyzing interaction delays in the field can hardly avoid LoAF anymore.

Its practical value shows up especially in complex applications with many third-party scripts, where attribution down to individual source files and functions can be the difference between hours of troubleshooting and a root cause identified in minutes. Gradually integrating it into existing real-user monitoring is the obvious next step for any team that takes INP seriously.

Feature Long Tasks API Long Animation Frames API
Threshold 50ms total duration 50ms total frame duration
Script attribution Only rough context (e.g. iframe) Individual scripts with source and function
Rendering portion visible No Yes, as a separate phase
Forced layout detectable No Yes, via forcedStyleAndLayoutDuration
Browser support Broad (partial Firefox included) Currently Chromium-based browsers

Mironsoft

Web performance, Core Web Vitals, and load time optimization

Load times that don't make users bounce before the page is even visible?

We review existing websites for slow Core Web Vitals, bloated JavaScript bundles, and unnecessary render blockers, then build a performance foundation that stays measurable instead of just looking good once.

Performance Audit

Systematically measuring and fixing Core Web Vitals, load waterfall, and render blockers.

Bundle Optimization

Specifically reducing JavaScript and CSS bundle size and improving code splitting.

Monitoring Setup

Establishing continuous performance monitoring instead of a one-time snapshot.

10. Zusammenfassung

Long Animation Frames API

Purpose

Detailed attribution of blocking main-thread frames

New vs. Long Tasks

Breakdown by script and rendering share

Application

Targeted debugging of INP regressions

Support

Chromium browsers, Firefox and Safari pending

11. FAQ: Long Animation Frames API

1What's the main difference between LoAF and the Long Tasks API?
LoAF provides a detailed breakdown by script and rendering share including individual function calls, while the Long Tasks API only reports the total duration and a rough context.
2At what duration does a LoAF entry appear?
Just like the Long Tasks API, the threshold is fifty milliseconds of total animation frame duration before an entry gets reported.
3What's in the scripts array of a LoAF entry?
The scripts array holds, for each script executed within the frame, details on invoker, source file, function name, and individual execution duration, enabling precise attribution.
4How do I detect forced layout thrashing with LoAF?
Via the forcedStyleAndLayoutDuration field within the individual script entries, which explicitly reports the delay caused by synchronous layout queries.
5How do I use LoAF for an INP regression?
You use the Event Timing API to find the time window of the affected interaction, then filter all LoAF entries whose time window overlaps with it to identify the responsible scripts.
6Which browsers support the Long Animation Frames API?
Chrome and other Chromium-based browsers currently support LoAF fully, while Firefox and Safari haven't implemented the API yet.
7Can I use LoAF and the Long Tasks API together?
Yes, both APIs can be registered at the same time, which is useful as a fallback in browsers without LoAF support to still get coarse data.
8How do I integrate LoAF data into my monitoring?
The most sensible approach is filtered transmission, collecting only LoAF entries that overlap in time with a poorly measured interaction, instead of sending every entry unfiltered.
9What does the renderStart field in a LoAF entry mean?
It marks the point at which the rendering phase of the frame begins, and serves as a reference point to separate the script portion from the rendering portion of the total duration.
10Does LoAF completely replace manual profiling in the DevTools?
Not entirely, but LoAF significantly reduces the need for manual profiling, since it already provides the relevant attribution data automatically and in real time, suitable for production.