Signatur
Beschreibung
Die schreibgeschützte Eigenschaft scripts eines PerformanceLongAnimationFrameTiming-Eintrags (Eintragstyp "long-animation-frame", kurz LoAF) enthält eine Liste von PerformanceScriptTiming-Objekten. Jedes Objekt steht für ein Skript, das während des langen Frames ausgeführt wurde und dabei mindestens 5 Millisekunden gedauert hat.
Genau damit wird die Long Animation Frames API so nützlich: Man erfährt nicht nur, dass ein Frame länger als 50 ms gebraucht hat, sondern auch, welcher Code verantwortlich war. Jedes Script-Timing liefert unter anderem invoker (z. B. "BUTTON#submit.onclick"), invokerType, sourceURL, sourceFunctionName, sourceCharPosition, duration, forcedStyleAndLayoutDuration und pauseDuration.
Typischerweise verwendet man die Eigenschaft im Real User Monitoring (RUM): Mit einem PerformanceObserver sammelt man lange Frames, wertet scripts aus und schickt die Angaben zu den Verursachern, etwa Drittanbieter-Skripte oder teure Event-Handler, an einen Analyse-Endpunkt. So lassen sich schlechte INP-Werte (Interaction to Next Paint) gezielt beheben.
Rückgabewert
PerformanceScriptTiming-Objekten. Es ist leer, wenn kein Skript im Frame mindestens 5 ms gedauert hat, zum Beispiel wenn Rendering oder Layout den Frame lang gemacht haben.Beispiele
Lange Frames beobachten und Skripte auflisten
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`Langer Frame: ${Math.round(entry.duration)} ms`);
for (const script of entry.scripts) {
console.log(
` ${script.invokerType} | ${script.invoker} | ` +
`${Math.round(script.duration)} ms | ${script.sourceURL}`
);
}
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
Teuerstes Skript pro Frame an ein RUM-Backend senden
const report = (data) => {
navigator.sendBeacon('/rum/loaf', JSON.stringify(data));
};
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.scripts.length === 0) continue;
const worst = entry.scripts.reduce((a, b) =>
b.duration > a.duration ? b : a
);
report({
frameDuration: Math.round(entry.duration),
blocking: Math.round(entry.blockingDuration),
invoker: worst.invoker,
source: worst.sourceURL,
fn: worst.sourceFunctionName,
charPos: worst.sourceCharPosition,
forcedLayout: Math.round(worst.forcedStyleAndLayoutDuration)
});
}
}).observe({ type: 'long-animation-frame', buffered: true });
Skriptzeit nach Herkunft (erst- vs. drittanbieter) summieren
const totals = new Map();
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
for (const { sourceURL, duration } of entry.scripts) {
const origin = sourceURL ? new URL(sourceURL).origin : '(unbekannt)';
totals.set(origin, (totals.get(origin) ?? 0) + duration);
}
}
console.table(
[...totals].map(([origin, ms]) => ({ origin, ms: Math.round(ms) }))
);
}).observe({ type: 'long-animation-frame', buffered: true });
// Wichtig · Fallstricke
- Browser-Kompatibilität: Die Eigenschaft gibt es nur in Chromium-Browsern (ab Chrome/Edge 123). Prüfe die Unterstützung vorher mit
PerformanceObserver.supportedEntryTypes.includes('long-animation-frame'). - 5-ms-Schwelle: Skripte, die kürzer als 5 ms laufen, werden nicht erfasst. Die Summe der
scripts[i].durationist deshalb oft kleiner als die Dauer des Frames. - Cross-Origin-Skripte: Bei fremden Skripten ohne CORS (
crossorigin-Attribut und passendemAccess-Control-Allow-Origin) sind Detailangaben wiesourceFunctionNameodersourceCharPositionleer bzw.-1. Skripte aus Cross-Origin-iframes erscheinen gar nicht. - Kein Aufrufstapel: Es wird nur der Einstiegspunkt erfasst, z. B. der Event-Handler oder Callback. Tief verschachtelte Verursacher sieht man nicht direkt.
- Leeres Array: Ein langer Frame kann auch durch Rendering, Style oder Layout entstehen. Dann ist
scriptsleer. Werte in diesem FallrenderStartundstyleAndLayoutStartaus. - Performance: Nutze
buffered: true, um frühe Frames nicht zu verpassen. Sende die Daten gebündelt pernavigator.sendBeacon()statt bei jedem Eintrag.