Signatur
Beschreibung
previousRect gehört zum Interface LayoutShiftAttribution aus der Layout Instability API. Jeder LayoutShift-Eintrag, den ein PerformanceObserver mit dem Typ "layout-shift" liefert, enthält im Array sources bis zu fünf solcher Attributionen. Jede davon beschreibt ein Element, das sich verschoben hat. Die Eigenschaft previousRect gibt an, wo dieses Element vor der Verschiebung lag. Das Gegenstück ist currentRect für die Position danach.
Das Rechteck ist ein DOMRectReadOnly mit x, y, width, height, top, right, bottom und left. Alle Werte sind CSS-Pixel relativ zum Viewport. Wenn man previousRect mit currentRect vergleicht, sieht man, wie weit und in welche Richtung sich ein Element bewegt hat. Das hilft beim Debuggen einer schlechten Cumulative Layout Shift (CLS), einem der Core Web Vitals.
Typische Einsatzfälle sind Real-User-Monitoring (RUM) und das Debuggen in der Entwicklung. Man findet damit heraus, welches Element sich wie stark bewegt hat, zum Beispiel durch nachgeladene Bilder ohne Größenangabe, eingeblendete Werbung oder Web-Fonts. Oft markiert man die betroffene Fläche danach sichtbar oder schickt die Werte an einen Analyse-Endpunkt.
Rückgabewert
width und height gleich 0.Beispiele
Vorherige Position aller verschobenen Elemente protokollieren
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Verschiebungen direkt nach Nutzereingaben zählen nicht zum CLS
if (entry.hadRecentInput) continue;
for (const source of entry.sources) {
const { x, y, width, height } = source.previousRect;
console.log('Element:', source.node);
console.log(`Vorher: x=${x}, y=${y}, ${width}×${height}`);
}
}
});
observer.observe({ type: 'layout-shift', buffered: true });
Verschiebungsweite aus previousRect und currentRect berechnen
const describeShift = (source) => {
const prev = source.previousRect;
const curr = source.currentRect;
return {
element: source.node?.nodeName ?? '(entfernt)',
deltaX: Math.round(curr.x - prev.x),
deltaY: Math.round(curr.y - prev.y),
};
};
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.hadRecentInput) continue;
console.table(entry.sources.map(describeShift));
}
}).observe({ type: 'layout-shift', buffered: true });
Alte Position im Debug-Modus sichtbar markieren
const highlightPrevious = (rect) => {
if (rect.width === 0 || rect.height === 0) return;
const box = document.createElement('div');
Object.assign(box.style, {
position: 'fixed',
left: `${rect.left}px`,
top: `${rect.top}px`,
width: `${rect.width}px`,
height: `${rect.height}px`,
outline: '2px dashed red',
pointerEvents: 'none',
zIndex: '99999',
});
document.body.append(box);
setTimeout(() => box.remove(), 2000);
};
if (PerformanceObserver.supportedEntryTypes?.includes('layout-shift')) {
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
entry.sources.forEach((s) => highlightPrevious(s.previousRect));
}
}).observe({ type: 'layout-shift', buffered: true });
} else {
console.warn('Layout Instability API wird nicht unterstützt.');
}
Zusammenfassung an einen Analyse-Endpunkt senden
const toPlain = ({ x, y, width, height }) => ({ x, y, width, height });
new PerformanceObserver((list) => {
const shifts = list.getEntries()
.filter((e) => !e.hadRecentInput)
.flatMap((e) => e.sources.map((s) => ({
value: e.value,
selector: s.node instanceof Element ? s.node.tagName.toLowerCase() + (s.node.id ? `#${s.node.id}` : '') : null,
previous: toPlain(s.previousRect),
current: toPlain(s.currentRect),
})));
if (shifts.length) {
navigator.sendBeacon('/rum/layout-shift', JSON.stringify(shifts));
}
}).observe({ type: 'layout-shift', buffered: true });
// Wichtig · Fallstricke
- Browser-Unterstützung: Die Layout Instability API gibt es nur in Chromium-basierten Browsern wie Chrome, Edge und Opera. Firefox und Safari unterstützen sie nicht. Vor der Nutzung sollte man mit
PerformanceObserver.supportedEntryTypes.includes('layout-shift')prüfen, ob sie verfügbar ist. - Relativ zum Viewport: Die Koordinaten beziehen sich auf den Viewport, nicht auf das Dokument. Wurde zwischen Messung und Auswertung gescrollt, muss man
window.scrollXundwindow.scrollYeinrechnen, wenn man dokumentbezogene Positionen braucht. - Nicht serialisierbar:
JSON.stringify(rect)funktioniert nur, weilDOMRectReadOnlyeinetoJSON()-Methode hat. Beim Spread-Operator ({...rect}) entsteht dagegen ein leeres Objekt, weil die Eigenschaften Getter am Prototyp sind. Deshalb die Felder besser einzeln übernehmen. - Leere Rechtecke: Wurde ein Element neu eingeblendet, kann
previousRectdie Breite und Höhe0haben. - Höchstens fünf Quellen:
sourcesenthält nur die fünf Elemente mit dem größten Einfluss. Kleinere Verschiebungen können daher fehlen. nodekannnullsein: Wurde das Element inzwischen aus dem DOM entfernt, bleibt nur das Rechteck als Hinweis übrig.- Einträge mit
hadRecentInput === truezählen nicht zum CLS und sollten bei der Auswertung meist herausgefiltert werden.