Signatur
Beschreibung
LayoutShift gehört zur Performance-API und steht für eine Layout-Verschiebung. Das passiert, wenn ein sichtbares Element zwischen zwei Frames seine Startposition ändert, zum Beispiel weil ein Bild ohne feste Maße nachlädt, ein Werbebanner eingefügt wird oder eine Web-Schrift die Textgröße verändert. Jeder Eintrag hat den entryType "layout-shift" und wird über einen PerformanceObserver gelesen.
Das Interface ist die Grundlage für die Core-Web-Vitals-Kennzahl Cumulative Layout Shift (CLS). Dafür werden die value-Werte aller Verschiebungen, die nicht kurz nach einer Nutzereingabe passiert sind, zu „Session-Fenstern“ zusammengefasst. Ein Fenster darf höchstens 5 Sekunden lang sein, und zwischen zwei Verschiebungen darf höchstens 1 Sekunde Pause liegen. CLS ist die Summe des größten dieser Fenster.
Wichtige Eigenschaften (zusätzlich zu denen von PerformanceEntry):
value(number): der Layout-Shift-Score, also Impact-Fraction × Distance-Fraction.hadRecentInput(boolean):true, wenn in den letzten 500 ms eine Nutzereingabe stattfand. Solche Verschiebungen gelten als erwartet und zählen nicht zum CLS.lastInputTime(DOMHighResTimeStamp): Zeitpunkt der letzten Eingabe.sources(LayoutShiftAttribution[]): bis zu fünf Elemente, die sich am stärksten verschoben haben, jeweils mitnode,previousRectundcurrentRect.toJSON(): gibt eine JSON-Darstellung des Eintrags zurück.
Einsatzgebiete sind Real-User-Monitoring (RUM), die Fehlersuche bei „springenden“ Seiten und die Suche nach den Elementen, die Verschiebungen auslösen. Einen eigenen Konstruktor gibt es nicht: Instanzen erzeugt ausschließlich der Browser.
Rückgabewert
PerformanceObserver (Typ "layout-shift"). performance.getEntriesByType() liefert für diesen Typ keine Einträge.Beispiele
Layout-Shifts mit PerformanceObserver beobachten
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Layout-Shift:', entry.value.toFixed(4),
'nach Eingabe:', entry.hadRecentInput,
'bei', Math.round(entry.startTime), 'ms');
}
});
// buffered: true liefert auch Verschiebungen, die vor der Registrierung passiert sind
observer.observe({ type: 'layout-shift', buffered: true });
CLS mit Session-Fenstern berechnen
let cls = 0;
let sessionValue = 0;
let sessionEntries = [];
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Verschiebungen nach Nutzereingaben ignorieren
if (entry.hadRecentInput) continue;
const first = sessionEntries[0];
const last = sessionEntries.at(-1);
// Neues Fenster, wenn Pause > 1 s oder Fenster länger als 5 s
if (sessionEntries.length &&
(entry.startTime - last.startTime > 1000 ||
entry.startTime - first.startTime > 5000)) {
sessionValue = 0;
sessionEntries = [];
}
sessionValue += entry.value;
sessionEntries.push(entry);
cls = Math.max(cls, sessionValue);
}
});
po.observe({ type: 'layout-shift', buffered: true });
// Beim Verlassen der Seite melden
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
navigator.sendBeacon('/analytics', JSON.stringify({ cls }));
}
});
Auslösende Elemente über sources finden
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.hadRecentInput || entry.value < 0.01) continue;
for (const source of entry.sources) {
const { node, previousRect, currentRect } = source;
console.log('Verschoben:', node?.nodeName ?? '(entfernt)',
`y ${previousRect.y} → ${currentRect.y}`);
}
}
}).observe({ type: 'layout-shift', buffered: true });
Unterstützung prüfen
const supportsLayoutShift =
PerformanceObserver.supportedEntryTypes?.includes('layout-shift') ?? false;
console.log('LayoutShift unterstützt:', supportsLayoutShift);
// Wichtig · Fallstricke
- Browser-Unterstützung: Nur Chromium-Browser (Chrome, Edge, Opera, Samsung Internet). Firefox und Safari unterstützen das Interface nicht. Prüfen Sie die Unterstützung vorher mit
PerformanceObserver.supportedEntryTypes. In Firefox und Safari liefert CLS-Monitoring deshalb keine Daten. - Zahlt nicht direkt in CLS ein: Einträge mit
hadRecentInput === truemüssen Sie selbst herausfiltern. Die bloße Summe allervalue-Werte entspricht nicht der offiziellen CLS-Definition mit Session-Fenstern. Für produktive Messungen empfiehlt sich die Bibliothekweb-vitals. - Nicht über getEntries abrufbar:
performance.getEntriesByType('layout-shift')liefert ein leeres Array. Verwenden SiePerformanceObservermitbuffered: true. - sources ist begrenzt: Die Liste enthält höchstens fünf Einträge.
nodekannnullsein, wenn das Element inzwischen aus dem DOM entfernt wurde. - Häufige Ursachen: Bilder und iframes ohne
width/heightbzw.aspect-ratio, nachträglich eingefügte Inhalte und Web-Fonts (FOUT). Animationen mittransformerzeugen keine Layout-Shifts. - Den CLS-Wert sollten Sie beim
visibilitychangeaufhiddenmelden, nicht beiunload. Das Ereignisunloadist unzuverlässig und verhindert den Back/Forward-Cache.