A click that visibly hangs, a menu that opens only after a pause, a form that stutters as you type -- these are the moments the Core Web Vital INP, Interaction to Next Paint, measures. It captures how long a page takes to respond visibly to an input, and in spring 2024 it replaced the older First Input Delay as one of the three central user-experience signals (web.dev). The problem: a poor INP value tells you that something stutters, but not which script blocks the frame. This is exactly the gap the Long Animation Frames API, or LoAF, closes. Shipped since Chrome 123, it names the long frame behind a sluggish interaction, along with the scripts that caused it -- with file name, duration and calling function (Chrome for Developers). Together with the open-source web-vitals library in version 4, this frame analysis can be attached directly to the slowest measured interaction. This article shows how an anonymous INP value becomes a concrete file you can act on -- the decisive diagnostic step in 2026, because INP is the most frequently missed of the three Core Web Vitals. In our Core Web Vitals optimization this attribution comes first, before we touch a single line of JavaScript.
Why INP Is So Hard to Pin Down
Of the three Core Web Vitals, INP is the one most sites fail. While loading time and visual stability sit in the green on many websites, about 23 percent (Web Almanac 2025) of mobile pages miss the INP threshold of 200 milliseconds at the 75th percentile. An INP counts as good when the near-slowest interaction of a session is answered visibly within 200 ms; between 200 and 500 ms the scale calls for improvement, above that it is poor (web.dev). What is measured, then, is not the average but the outlier -- the one janky interaction that sticks in the user's memory. What INP measures precisely and which fundamental levers exist is summed up in our guide to INP optimization.
The reason for its awkwardness lies in the nature of the metric. INP arises not during loading but during use -- with every click, tap or keystroke across the entire dwell time (web.dev). It depends on the amount and distribution of JavaScript, on event handlers, on embedded third-party scripts and on how heavily the browser's main thread is loaded. Unlike Largest Contentful Paint, which we break into four subparts, INP cannot be pinned to a single resource. The cause can lie in any of the many scripts that run during an interaction -- and for a long time there was no tool that named them unambiguously.
The Three Phases of an Interaction
Every interaction that INP measures breaks into three consecutive phases -- much as LCP breaks into its subparts. First, the input delay: the time from the tap until the associated event handler actually starts. It is often large when the main thread is still busy with another task. Then the processing time: the duration in which the event handlers run and do their work. Finally, the presentation delay: the time until the browser paints the result as the next frame on the screen (web.dev). Added together, the three phases make up the latency that INP records for this interaction.
For diagnosis it is decisive in which phase the time is lost. A high input delay points to an overloaded main thread -- often caused by scripts still being processed at interaction time, such as late-loaded third-party scripts that slow the page down. A high processing time reveals heavy event handlers that do too much at once. A high presentation delay points to expensive layout or render work after the handler. The Long Animation Frames API targets exactly where most of the time goes: the scripts that make the frame long.
Input Delay
The time from the tap until the event handler starts. If it is high, another task is blocking the main thread -- the response begins late.
Processing Time
The duration in which the event handlers run. Heavy handlers that do too much at once drive this phase up.
Presentation Delay
The time until the next frame is painted. Expensive layout and render work after the handler noticeably lengthens it.
What a Long Animation Frame Is
A long animation frame is a browser render cycle that took longer than 50 milliseconds (Chrome for Developers). The browser tries to paint a new image at short intervals; if a frame takes markedly longer, the interface stutters and interactions respond sluggishly. The Long Animation Frames API reports each such long frame as an entry with a series of timestamps: the start time, the total duration, the beginning of the render and layout phase and -- most importantly -- a blocking duration that states how long the main thread was blocked for other work in that frame (Chrome for Developers).
The real progress, however, lies in the scripts field: for each long frame the API provides a list of the scripts that contributed to its length -- each with source URL, duration, calling function and the type of the call, such as an event handler or a timer (Chrome for Developers). For the first time this makes it possible to say from the outside, without laborious profiling: this frame was long, and this concrete file is responsible for it. The API also records whether a user interaction took place in the frame -- so it links the long frame to exactly the response the visitor experienced as janky.
What the LoAF API Provides per Long Frame
Why the Old Long Tasks API Was Not Enough
A tool for long tasks existed before: the Long Tasks API. It too reports every task on the main thread that lasts longer than 50 milliseconds (MDN Web Docs). But it had a decisive weakness for finding causes: it told you that a long task ran and how long, but practically nothing about which code triggered it. As attribution it usually delivered only a coarse container hint -- for example that the task came from a same-origin script -- without a file name and without a function. In practice that meant: you knew something was stuck, but not where.
The Long Animation Frames API closes this gap twice over. First, it looks not at individual tasks but at the whole frame -- so it also captures the interplay of several short tasks, layout and rendering that together make a frame long, even though no single task crosses the 50-millisecond mark. Second, it delivers the script attribution the Long Tasks API lacked. This makes it the more precise tool for getting to the bottom of INP -- precisely because INP often does not hang on a single long task but on many small ones that add up to a sluggish frame.
| Feature | Long Tasks API | Long Animation Frames API |
|---|---|---|
| Unit of capture | Single task over 50 ms | Whole frame over 50 ms |
| Script attribution | Only a coarse origin hint | Source URL, function and duration per script |
| Render and layout time | not included | Included as its own timestamp |
| Link to the interaction | None direct | Interaction in the frame is flagged |
| Availability | Broadly supported | Chromium-based browsers (from Chrome 123) |
web-vitals v4 Attaches LoAF to the Interaction
Using the raw LoAF API is powerful but cumbersome. This is where the open-source web-vitals library comes in: its attribution build measures the INP of a real user session and, since version 4, attaches the matching long animation frame entries directly to the measured interaction (web-vitals documentation). So you get not just the number but the element that was tapped, the split into input delay, processing and presentation -- and the scripts of the associated long frame. This is the bridge from lab measurement to real user experience: the attribution comes from the field, from real devices and networks.
import { onINP } from 'web-vitals/attribution';
onINP((metric) => {
const a = metric.attribution;
// interaction target and the three phases
console.log(a.interactionTarget); // e.g. button#buy
console.log(a.inputDelay); // input delay in ms
console.log(a.processingDuration); // processing time in ms
console.log(a.presentationDelay); // presentation delay in ms
// long animation frames around the interaction, with scripts
for (const frame of a.longAnimationFrameEntries) {
for (const s of frame.scripts) {
console.log(s.sourceURL, Math.round(s.duration), s.invoker);
}
}
});In practice this means: for every slow interaction that occurs in the field, the target button, the phase split and the list of the script URLs involved land in your analytics stream. Whoever collects this data -- for instance through their own real-user monitoring, as we describe in the relationship between field data from RUM and CrUX and performance budgets -- sees not only that INP is poor on certain pages but which file regularly causes the long frame there. An anonymous percentile value becomes a nameable task.
How to Read the LoAF Data in Practice
For manual diagnosis a short PerformanceObserver in the browser console is enough. It subscribes to the long frames and prints, for each, the blocking duration and the scripts involved -- ideal for deliberately reproducing an interaction on a suspect page and seeing what happens in the frame.
const obs = new PerformanceObserver((list) => {
for (const frame of list.getEntries()) {
if (frame.blockingDuration > 0) {
console.log('Frame', Math.round(frame.duration) + ' ms',
'blocking', Math.round(frame.blockingDuration) + ' ms');
for (const s of frame.scripts) {
console.log(' -', s.sourceURL, Math.round(s.duration) + ' ms', s.invoker);
}
}
}
});
obs.observe({ type: 'long-animation-frame', buffered: true });In addition, the performance panel of the Chrome developer tools now shows long frames visually on the timeline, and the Lighthouse diagnostic report flags long main-thread tasks together with the scripts that cause them. Which findings there point to an INP problem and how to order the list sensibly is covered in our article on reading and prioritizing the Lighthouse report. What remains important: measure in the lab with realistic throttling, because a fast office laptop hides exactly the long frames that hurt most on an average smartphone.
LoAF Is a Chromium Tool
From Frame to a Prioritized Measure
The value of the attribution lies in the order it forces. Instead of demanding 'less JavaScript' across the board, it names the one file that shows up repeatedly in the long frames of the slowest interactions. If it comes from an embedded third party, the question is: does the page need this script here, can it be deferred or swapped for a lighter integration? If it comes from your own code, the lever is usually to relieve the heavy event handler -- do less work synchronously and defer the rest.
The most effective technique against a high processing time is to actively break long tasks into smaller chunks so the browser can respond to input in between. How to do this with modern built-in means is shown in our article on breaking up long tasks with scheduler.yield. So that a good value once reached does not tip over again at the next deployment, an upper limit for script time and long frames belongs in the delivery chain -- how a performance budget in CI stops regressions automatically we describe separately.
- Measure INP in the field and filter out the pages with the worst values
- Use the web-vitals attribution to capture the slowest interaction per page with its phase split
- From the associated long animation frames, determine the script with the largest time share
- Check in which phase the time is lost -- input, processing or presentation
- Address only that one cause: tame the third-party script, relieve the handler or split the task
- Measure again in the field after the change -- does a different script appear as the new bottleneck?
The Core in One Sentence
Before the LoAF API we groped in the fog with INP problems and tried script after script. Today we read the offending script straight from the frame and know, before the first change, where the lever is.
This is exactly how we proceed in a structured performance audit: gather INP in the field, trace the slowest interactions back to their scripts via the Long Animation Frames API, and build an action list sorted by impact -- instead of a blanket rebuild of the frontend delivery. The bottleneck often shifts to the next script after the first change, which is why the measurement is repeated until the interactions stay reliably under the threshold. Which steps this includes is shown in our overview of performance services; how INP fits into the interplay of the three metrics is explored in the article on Core Web Vitals 2026.