Long Animation Frames
This snippet requires specific features that are either available in Chrome 115+ for origins that are part of the LoAF Origin Trial (opens in a new tab) or require that you enable the #enable-experimental-web-platform-features flag in chrome://flags (please note that you will need to restart your browser).
To determine when long animation frames (LoAF) happen, you can use PerformanceObserver (opens in a new tab) and register to observe entries of type long-animation-frame. This snippet from @noamr (opens in a new tab) provides additional informations computed from the LoAF raw data.
Snippet
(function init() {
function processAndFilterLoAFs(entries) {
function floorObject(o) {
return Object.fromEntries(Array.from(Object.entries(o)).map(([key, value]) =>
[key, typeof value === "number" ? Math.floor(value) :
value]))
}
function processEntry(entry) {
const startTime = entry.startTime;
const endTime = entry.startTime + entry.duration;
const delay = entry.desiredRenderStart ? Math.max(0, entry.startTime - entry.desiredRenderStart) : 0;
const deferredDuration = Math.max(0, entry.desiredRenderStart - entry.startTime);
const renderDuration = entry.styleAndLayoutStart - entry.renderStart;
const workDuration = entry.renderStart ? entry.renderStart - entry.startTime : entry.duration;
const totalForcedStyleAndLayoutDuration = entry.scripts.reduce((sum, script) => sum + script.forcedStyleAndLayoutDuration, 0);
const styleAndLayoutDuration = entry.styleAndLayoutStart ? endTime - entry.styleAndLayoutStart : 0;
const scripts = entry.scripts.map(script => {
const delay = script.startTime - script.desiredExecutionStart;
const scriptEnd = script.startTime + script.duration;
const compileDuration = script.executionStart - script.startTime;
const execDuration = scriptEnd - script.executionStart;
return floorObject({delay, compileDuration, execDuration, ...script.toJSON()});
})
return floorObject({startTime, delay, deferredDuration, renderDuration, workDuration, styleAndLayoutDuration, totalForcedStyleAndLayoutDuration, ...entry.toJSON(), scripts});
}
return entries.map(processEntry);
}
function analyze() {
return loafs.map(loaf => (
{
blockingDuration: loaf.blockingDuration,
loaf,
scripts: loaf.scripts, events: events.filter(e => overlap(e, loaf))
})).filter(l => (l.blockingDuration && l.events.length));
}
let loafs = [];
let events = [];
function processLoAFs(entries) {
loafs = [...loafs, ...processAndFilterLoAFs(entries.getEntries())];
console.log(analyze());
}
function processEvents(entries) {
events = [...events, ...entries.getEntries()];
console.log(analyze());
}
new PerformanceObserver(processLoAFs).observe(
{type: "long-animation-frame", buffered:true});
new PerformanceObserver(processEvents).observe(
{type: "event", buffered:true});
function overlap(e1, e2) {
return e1.startTime < (e2.startTime + e2.duration) &&
e2.startTime < (e1.startTime + e1.duration)
}
window.whynp = analyze;
}
)()