Start · Sprachen · JavaScript · Referenz · PerformanceScriptTiming.forcedStyleAndLayoutDuration

PerformanceScriptTiming.forcedStyleAndLayoutDuration

Eigenschaft

Liefert die Gesamtzeit in Millisekunden, die ein Script mit erzwungener, synchroner Style- und Layout-Berechnung (Layout Thrashing) verbracht hat.

seit JavaScript Chrome/Edge 123+ (experimentell, Teil der Long Ani Kategorie: webapi

Signatur

PerformanceScriptTiming.forcedStyleAndLayoutDuration

Beschreibung

Die schreibgeschützte Eigenschaft forcedStyleAndLayoutDuration des PerformanceScriptTiming-Interfaces gibt einen DOMHighResTimeStamp zurück. Er gibt an, wie viele Millisekunden ein Script insgesamt mit erzwungener synchroner Style- und Layout-Berechnung verbracht hat. Dazu kommt es, wenn ein Script zuerst das DOM oder Styles verändert und danach sofort geometrische Werte wie offsetHeight, getBoundingClientRect() oder getComputedStyle() liest. Der Browser muss dann Style und Layout sofort neu berechnen, statt damit bis zum nächsten Frame zu warten.

PerformanceScriptTiming-Einträge kommen aus der Long Animation Frames API (LoAF). Sie stehen im Array scripts eines PerformanceLongAnimationFrameTiming-Eintrags (Typ "long-animation-frame"). Mit dieser Eigenschaft findest du heraus, welches Script durch sogenanntes Layout Thrashing einen langen Frame und damit eine schlechte Interaction to Next Paint (INP) verursacht.

Werte deutlich über 0 sind ein klarer Hinweis, Lese- und Schreibzugriffe auf das DOM zu bündeln. Das geht zum Beispiel so: erst alle Werte lesen, dann alle Änderungen schreiben. Oder du verschiebst Schreibzugriffe mit requestAnimationFrame(). Vergleiche den Wert mit duration des Script-Eintrags, dann siehst du, wie groß der Anteil an der Laufzeit ist.

Rückgabewert

Typ
DOMHighResTimeStamp (number)
Beschreibung
Gesamtdauer der erzwungenen Style- und Layout-Berechnungen dieses Scripts in Millisekunden. 0, wenn keine erzwungene Berechnung stattgefunden hat.

Beispiele

Scripts mit erzwungenem Layout erkennen

const observer = new PerformanceObserver((list) => {
  for (const frame of list.getEntries()) {
    for (const script of frame.scripts) {
      if (script.forcedStyleAndLayoutDuration > 0) {
        console.log(
          `${script.sourceURL || script.invoker}: ` +
          `${script.forcedStyleAndLayoutDuration.toFixed(1)} ms erzwungenes Layout`
        );
      }
    }
  }
});

observer.observe({ type: 'long-animation-frame', buffered: true });
https://example.com/app.js: 42.3 ms erzwungenes Layout

Anteil an der Script-Laufzeit berechnen

const report = (script) => {
  const anteil = script.duration > 0
    ? (script.forcedStyleAndLayoutDuration / script.duration) * 100
    : 0;
  return {
    quelle: script.sourceFunctionName || script.invoker,
    dauer: Math.round(script.duration),
    layout: Math.round(script.forcedStyleAndLayoutDuration),
    anteilProzent: Math.round(anteil)
  };
};

new PerformanceObserver((list) => {
  const kandidaten = list.getEntries()
    .flatMap((frame) => frame.scripts)
    .filter((s) => s.forcedStyleAndLayoutDuration > 10)
    .map(report);
  if (kandidaten.length) console.table(kandidaten);
}).observe({ type: 'long-animation-frame', buffered: true });
┌─────────┬──────────────┬───────┬────────┬───────────────┐ │ (index) │ quelle │ dauer │ layout │ anteilProzent │ │ 0 │ 'resizeAll' │ 120 │ 85 │ 71 │ └─────────┴──────────────┴───────┴────────┴───────────────┘

Layout Thrashing auslösen und beheben

const boxes = [...document.querySelectorAll('.box')];

// Schlecht: abwechselnd lesen und schreiben -> hohe forcedStyleAndLayoutDuration
const resizeSchlecht = () => {
  for (const box of boxes) {
    box.style.width = `${box.parentElement.offsetWidth / 2}px`;
  }
};

// Besser: erst alles lesen, dann alles schreiben
const resizeGut = () => {
  const breiten = boxes.map((box) => box.parentElement.offsetWidth);
  boxes.forEach((box, i) => {
    box.style.width = `${breiten[i] / 2}px`;
  });
};

document.querySelector('#resize').addEventListener('click', resizeGut);

// Wichtig · Fallstricke

  • Browser-Kompatibilität: Die Long Animation Frames API ist experimentell und derzeit nur in Chromium-Browsern verfügbar (ab Chrome/Edge 123). Firefox und Safari unterstützen sie nicht. Prüfe die Unterstützung vorher mit PerformanceObserver.supportedEntryTypes.includes('long-animation-frame').
  • Einträge gibt es nur für Frames über 50 ms und nur für Scripts, die mindestens 5 ms laufen. Kurzes Layout Thrashing taucht daher gar nicht auf.
  • Die Zeit ist bereits in duration des Script-Eintrags enthalten. Addiere sie also nicht noch einmal dazu.
  • Bei Cross-Origin-Scripts ohne passende CORS-Freigabe werden manche Attributionsdaten (z. B. sourceURL) aus Datenschutzgründen eingeschränkt. Dann weißt du eventuell nicht, welches Script verantwortlich ist.
  • Die Genauigkeit des Zeitstempels kann der Browser reduzieren, um Timing-Angriffe zu erschweren.