Start · Sprachen · JavaScript · Referenz · LayoutShiftAttribution.previousRect

LayoutShiftAttribution.previousRect

Eigenschaft

Schreibgeschützte Eigenschaft, die als <code>DOMRectReadOnly</code> angibt, wo sich ein verschobenes Element im Viewport befand, bevor es sich verschoben hat.

seit JavaScript Chromium-basierte Browser (Chrome/Edge 84+); nicht Kategorie: webapi

Signatur

LayoutShiftAttribution.previousRect

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

Typ
DOMRectReadOnly
Beschreibung
Ein unveränderliches Rechteck mit der Position und Größe des Elements vor der Verschiebung, in CSS-Pixeln relativ zum Viewport. War das Element vorher nicht sichtbar (etwa weil es neu eingeblendet wurde), kann das Rechteck leer sein, also 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 });
Element: <img class="hero"> Vorher: x=0, y=120, 800×400

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 });
┌─────────┬─────────┬────────┬────────┐ │ (index) │ element │ deltaX │ deltaY │ ├─────────┼─────────┼────────┼────────┤ │ 0 │ 'DIV' │ 0 │ 250 │ └─────────┴─────────┴────────┴────────┘

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.scrollX und window.scrollY einrechnen, wenn man dokumentbezogene Positionen braucht.
  • Nicht serialisierbar: JSON.stringify(rect) funktioniert nur, weil DOMRectReadOnly eine toJSON()-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 previousRect die Breite und Höhe 0 haben.
  • Höchstens fünf Quellen: sources enthält nur die fünf Elemente mit dem größten Einfluss. Kleinere Verschiebungen können daher fehlen.
  • node kann null sein: Wurde das Element inzwischen aus dem DOM entfernt, bleibt nur das Rechteck als Hinweis übrig.
  • Einträge mit hadRecentInput === true zählen nicht zum CLS und sollten bei der Auswertung meist herausgefiltert werden.