Signatur
Beschreibung
PerformancePaintTiming ist Teil der Paint Timing API und liefert Leistungseinträge vom Typ "paint". Jeder Eintrag steht für einen Render-Meilenstein beim Laden einer Seite. Zwei gibt es:
first-paint(FP): Zeitpunkt, zu dem der Browser zum ersten Mal irgendetwas zeichnet, das sich vom Standardhintergrund unterscheidet, z. B. eine Hintergrundfarbe.first-contentful-paint(FCP): Zeitpunkt, zu dem erstmals DOM-Inhalt gezeichnet wird, also Text, Bilder (auch Hintergrundbilder), nicht-weiße<canvas>-Elemente oder SVG.
Das Interface hat keinen eigenen Konstruktor und keine zusätzlichen Methoden. Es erbt alles von PerformanceEntry: name ist "first-paint" oder "first-contentful-paint", entryType ist immer "paint", startTime ist der Zeitpunkt des Paints in Millisekunden relativ zu performance.timeOrigin, und duration ist immer 0.
Einsatzbereich ist vor allem das Real User Monitoring (RUM): FCP gehört zu den zentralen Web-Vitals-Kennzahlen dafür, wie schnell eine Seite wahrgenommen wird. Die Einträge liest man am besten mit einem PerformanceObserver und der Option buffered: true aus. So bekommt man auch Paints mit, die schon vor der Registrierung des Observers passiert sind. Alternativ geht performance.getEntriesByType("paint").
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $name | string | Geerbte Eigenschaft (nur lesbar): "first-paint" oder "first-contentful-paint". |
|
| $entryType | string | "paint" | Geerbte Eigenschaft (nur lesbar): immer "paint". |
| $startTime | DOMHighResTimeStamp | Geerbte Eigenschaft (nur lesbar): Zeitpunkt des Paints in ms seit performance.timeOrigin. |
|
| $duration | number | 0 | Geerbte Eigenschaft (nur lesbar): bei Paint-Einträgen immer 0. |
Rückgabewert
PerformanceObserver oder performance.getEntriesByType("paint") abgerufen. toJSON() liefert eine JSON-fähige Darstellung des Eintrags.Beispiele
FCP mit PerformanceObserver messen
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.startTime.toFixed(1)} ms`);
}
});
// buffered: true liefert auch Einträge, die schon vor dem Registrieren entstanden sind
observer.observe({ type: "paint", buffered: true });
Paint-Einträge nachträglich abfragen
window.addEventListener("load", () => {
const paints = performance.getEntriesByType("paint");
const fcp = paints.find((e) => e.name === "first-contentful-paint");
if (fcp) {
console.log(fcp instanceof PerformancePaintTiming); // true
console.log(fcp.toJSON());
} else {
console.log("Noch kein FCP erfasst");
}
});
FCP an einen Analytics-Endpunkt senden
const reportFCP = () => {
const observer = new PerformanceObserver((list) => {
const fcp = list.getEntriesByName("first-contentful-paint")[0];
if (!fcp) return;
const payload = JSON.stringify({
metric: "FCP",
value: Math.round(fcp.startTime),
page: location.pathname,
});
navigator.sendBeacon("/analytics/vitals", payload);
observer.disconnect(); // FCP tritt nur einmal auf
});
observer.observe({ type: "paint", buffered: true });
};
if (PerformanceObserver.supportedEntryTypes?.includes("paint")) {
reportFCP();
}
// Wichtig · Fallstricke
- Browser-Unterstützung: Chromium-Browser unterstützen beide Einträge, Firefox seit Version 84. Safari (ab 14.1) meldet nur
first-contentful-paint, keinfirst-paint. Vorher mitPerformanceObserver.supportedEntryTypesprüfen, ob"paint"unterstützt wird. - Hintergrund-Tabs: Wird die Seite im Hintergrund geladen (
document.visibilityState === "hidden"), erzeugt der Browser eventuell gar keine Paint-Einträge, oder sie liefern stark verzerrte Werte. Für RUM-Messungen solche Seitenaufrufe herausfiltern. - Ohne
buffered: trueverpasst der Observer oft den FCP, weil er meist schon vor dem Registrieren des Observers passiert ist. - Ein Eintrag entsteht nur einmal pro Seitenladevorgang. Bei SPA-Routenwechseln gibt es keine neuen Paint-Einträge; dafür eignen sich
largest-contentful-paintoder eigeneperformance.mark()-Marken. - Neuere Chromium-Versionen ergänzen experimentell die Eigenschaften
paintTimeundpresentationTime. Diese sind noch nicht browserübergreifend verfügbar. - Die Werte werden aus Datenschutzgründen (Timing-Angriffe) gröber aufgelöst. Sie reichen für Kennzahlen, aber nicht für Messungen im Mikrosekundenbereich.