Start · Sprachen · JavaScript · Referenz · PerformanceLongAnimationFrameTiming.renderStart

PerformanceLongAnimationFrameTiming.renderStart

Eigenschaft

Gibt als <code>DOMHighResTimeStamp</code> an, wann der Rendering-Zyklus eines langen Animation-Frames begonnen hat, oder <code>0</code>, wenn in diesem Frame nicht gerendert wurde.

seit JavaScript Chrome/Edge 123+ (nicht Baseline, kein Firefox/Saf Kategorie: webapi

Signatur

get PerformanceLongAnimationFrameTiming.renderStart

Beschreibung

Die schreibgeschützte Eigenschaft renderStart gehört zur Long Animation Frames API (LoAF). Sie gibt den Zeitpunkt an, an dem der Browser innerhalb eines langen Animation-Frames (Eintragstyp "long-animation-frame", Dauer über 50 ms) mit dem Rendering-Zyklus begonnen hat. Der Wert ist relativ zu performance.timeOrigin, genau wie startTime.

Zum Rendering-Zyklus zählen die requestAnimationFrame()-Callbacks, die Berechnung von Stilen und Layout sowie die Callbacks von ResizeObserver und IntersectionObserver. Zusammen mit styleAndLayoutStart, startTime und duration lässt sich ein langer Frame in einzelne Phasen zerlegen:

  • Skript-/Task-Phase: renderStart - startTime
  • rAF-Callbacks: styleAndLayoutStart - renderStart
  • Stil & Layout (inkl. Observer): startTime + duration - styleAndLayoutStart
  • Gesamte Rendering-Phase: startTime + duration - renderStart

Das hilft bei der Frage, ob eine träge Interaktion (schlechter INP-Wert) durch langes JavaScript in Event-Handlern oder durch teures Rendering, etwa aufwendige rAF-Callbacks oder erzwungenes Layout, verursacht wurde.

Rückgabewert

Typ
DOMHighResTimeStamp (number)
Beschreibung
Zeitstempel in Millisekunden relativ zu performance.timeOrigin, an dem der Rendering-Zyklus begann. Hat der Frame nicht gerendert (z. B. nur ein langer Task ohne sichtbare Aktualisierung), ist der Wert 0.

Beispiele

renderStart langer Frames beobachten

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(
      `LoAF: Start ${entry.startTime.toFixed(1)} ms, ` +
      `Render-Start ${entry.renderStart.toFixed(1)} ms, ` +
      `Dauer ${entry.duration.toFixed(1)} ms`
    );
  }
});

// buffered: true liefert auch bereits aufgezeichnete Einträge
observer.observe({ type: 'long-animation-frame', buffered: true });
LoAF: Start 1523.4 ms, Render-Start 1601.2 ms, Dauer 96.3 ms

Langen Frame in Phasen zerlegen

const analyzeFrame = (entry) => {
  const end = entry.startTime + entry.duration;

  // Kein Rendering in diesem Frame
  if (entry.renderStart === 0) {
    return { script: entry.duration, raf: 0, styleLayout: 0 };
  }

  return {
    script: entry.renderStart - entry.startTime,
    raf: entry.styleAndLayoutStart - entry.renderStart,
    styleLayout: end - entry.styleAndLayoutStart
  };
};

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    const phases = analyzeFrame(entry);
    console.table(
      Object.fromEntries(
        Object.entries(phases).map(([k, v]) => [k, `${v.toFixed(1)} ms`])
      )
    );
  }
}).observe({ type: 'long-animation-frame', buffered: true });
┌─────────────┬──────────┐ │ script │ 62.4 ms │ │ raf │ 18.1 ms │ │ styleLayout │ 15.8 ms │ └─────────────┴──────────┘

Rendering-lastige Frames an Analytics melden (mit Feature-Erkennung)

const supportsLoAF =
  PerformanceObserver.supportedEntryTypes?.includes('long-animation-frame');

if (supportsLoAF) {
  new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      if (entry.renderStart === 0) continue;

      const renderTime = entry.startTime + entry.duration - entry.renderStart;

      // Nur melden, wenn das Rendering den Großteil des Frames ausmacht
      if (renderTime > entry.duration / 2) {
        navigator.sendBeacon('/analytics/loaf', JSON.stringify({
          renderTime: Math.round(renderTime),
          duration: Math.round(entry.duration),
          url: location.pathname
        }));
      }
    }
  }).observe({ type: 'long-animation-frame', buffered: true });
} else {
  console.info('Long Animation Frames API wird nicht unterstützt.');
}

// Wichtig · Fallstricke

  • Wert 0: Wenn in einem langen Frame nichts gerendert wurde, ist renderStart gleich 0. Prüfen Sie das immer vor Differenzberechnungen, sonst erhalten Sie unsinnige negative oder riesige Werte.
  • Browser-Unterstützung: Die API gibt es nur in Chromium-basierten Browsern (Chrome/Edge ab 123). Firefox und Safari unterstützen sie nicht. Prüfen Sie die Unterstützung vorher mit PerformanceObserver.supportedEntryTypes.includes('long-animation-frame').
  • Nur lange Frames: Einträge entstehen erst ab einer Frame-Dauer von über 50 ms. Kurze Frames erscheinen nicht und lassen sich darüber nicht messen.
  • Genauigkeit: Aus Datenschutzgründen (Schutz vor Timing-Angriffen) runden Browser die Zeitstempel ab. Ohne Cross-Origin-Isolation ist die Auflösung gröber.
  • Nicht über getEntriesByType(): Die Einträge bekommen Sie zuverlässig nur per PerformanceObserver. Mit buffered: true erhalten Sie auch Einträge, die vor der Registrierung aufgezeichnet wurden.