Skip to main content

Measuring Long Animation Frames (LoAFs) In The Browser

· 4 min read

Web performance browser APIs help developers understand what causes slow experiences for real users on their website. The proposed Long Animation Frames (LoAF) API surfaces debug data about long CPU tasks and unresponsive page interactions.

LoAF would be especially useful to debug Google new Core Web Vitals metric, Interaction to Next Paint.

What is a long animation frame?

For a website to feel fast the browser needs to be able to update content quickly. Each update that's shown to the user is a frame. However, CPU tasks on the browser main thread can delay rendering resulting in long animation frames.

You can see rendering frames in the Chrome DevTools Performance tab. The red entries in the recording show dropped frames and the yellow entries show partially presented frames.

What is the Long Animation Frames API?

The Long Animation Frames API lets developers measure long frames on their websites, for example to monitor and understand page speed for real users.

The API provides insight on delayed the frame and what scripts on the page are responsible for the delay.

LoAF API implementation status

LoAF is a proposed API that has not been fully standardized yet. You have two options to use it:

  1. Enable Experimental Web Platform features in chrome://flags
  2. Sign up for the LoAF API origin trial to enable it for your website

Chrome flags

Example use of the Long Animation Frames API

Once the API has been enabled long-animation-frame entries will be collected and returned by performance.getEntries().

Long Animation frames screenshot

A deeper look at LoAF data

Let's look at a long animation frame during the initial render of the Asana homepage. We can compare the LoAF data to what we see in the DevTools Performance tab.

DevTools Performance recording

Script Attribution

We can look at the scripts entry of the performance entry to the duration of individual tasks.

const loafEntry = performance.getEntriesByName('long-animation-frame')
.filter(e => e.duration > 200)[0]
console.log(loafEntry.scripts.map(s => s.duration))
// [7, 10, 301, 19]

You can see that this matches the data reported by DevTools.

DevTools frame breakdown

The name property of each script tells us what triggered the script to run. In this case the script URL is shown in most cases because we are looking at the initial load of the JavaScript bundles on the page.

console.log(loafEntry.scripts.map(s => s.name).join("\n"))
/*
https://asana.com/_next/static/chunks/main-381f053c1076c06e.js
https://asana.com/_next/static/chunks/pages/_app-56ef24c772d01e47.js
https://asana.com/_next/static/chunks/pages/%5B%5B...slug%5D%5D-2a55f03d1dccb1ac.js
FrameRequestCallback
*/

FrameRequestCallback is different, it indicates a requestAnimationFrame call on the page. We can look at the source location to

console.log(loafEntry.scripts[3].sourceLocation)
// e@https://asana.com/_next/static/chunks/334.4d2c16e33b4811bb.js:4371

If we look for the 4371st character in the script we find the e function that's used as the requestAnimationFrame callback.

Script code at the reported location

The name property can also identify event handlers like DOMWindow.onresize when the window size changes.

Identifying a forced synchronous layout

Looking at the longest script in more detail find that 112 milliseconds are spent on forcedStyleAndLayoutDuration.

Forced restyle in LoAF

This matches the forced synchronous layout reported in DevTools.

DevTools layout thrashing

Understanding LoAF data for a simplified scenario

We've created a simple page with two slow click event handlers attached to a button:

  • The first click handler in script-2.js runs for 1 second
  • The second click handler in script-3.js runs for 2 seconds

Both call a blockCPU function in script-1.js.

DevTools simple click events

What does the LoAF data say? We can see that all activity is attributed to script-2.js and script-3.js. LoAF attribution looks at how a task is triggered, not what file most time is spent in.

When the button is clicked Chrome knows that it should run both handlers. But it takes a while before the second handler can run, so we see a one second delay between the desiredExecutionStart and the executionStart of script-3.js.

LoAF script attribution breakdown

What if we click the button a second time while the first event handlers are still running? We now get four entries in the scripts array, one for each time an event handler starts running.

Script handlers list in LoAF data

Long frames caused by user interactions

If an event handler is slow you will see the element selector and event type shown in the script name, for example BUTTON#example3.onclick.

You can also look at the firstUIEventTimestamp value of the LoAF entry to identify whether an event was handled. If that value isn't zero the animation frame likely causes slow Interaction to Next Paint.

LoAF with event timestamp and event handler info

Long animation frames in DebugBear

If the LoAF API is enabled on your website DebugBear real user monitoring can collect this data and report it as part of each page view.

DebugBear RUM showing long animation frame details

Get a monthly email with page speed tips