Veröffentlicht: 31. März 2014
Um Leistungsengpässe im Renderingpfad zu identifizieren und zu beheben, müssen Sie die häufigsten Fallstricke kennen. Eine Demo zur Identifizierung häufiger Leistungsmuster hilft Ihnen, Ihre Seiten zu optimieren.
Durch die Optimierung des kritischen Rendering-Pfads kann der Browser die Seite so schnell wie möglich anzeigen. Schnellere Seiten führen zu mehr Interaktionen, mehr aufgerufenen Seiten und höheren Conversions. Um die Zeit zu minimieren, die ein Besucher vor einem leeren Bildschirm verbringt, müssen wir optimieren, welche Ressourcen geladen werden und in welcher Reihenfolge.
Um diesen Prozess zu veranschaulichen, beginnen wir mit dem einfachsten Fall und bauen die Seite schrittweise mit zusätzlichen Ressourcen, Stilen und Anwendungslogik auf. Dabei optimieren wir jeden Fall und sehen uns auch an, wo es zu Problemen kommen kann.
Bisher haben wir uns ausschließlich darauf konzentriert, was im Browser passiert, nachdem die Ressource (CSS-, JS- oder HTML-Datei) zur Verarbeitung verfügbar ist. Die Zeit, die zum Abrufen der Ressource aus dem Cache oder aus dem Netzwerk benötigt wird, wurde ignoriert. Wir gehen von Folgendem aus:
- Ein Netzwerk-Roundtrip (Propagationslatenz) zum Server dauert 100 ms.
- Die Serverantwortzeit beträgt 100 ms für das HTML-Dokument und 10 ms für alle anderen Dateien.
Hello World
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Critical Path: No Style</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
Beginnen Sie mit einfachem HTML-Markup und einem einzelnen Bild. und ohne CSS oder JavaScript. Öffnen Sie dann den Bereich „Netzwerk“ in den Chrome-Entwicklertools und sehen Sie sich die resultierende Ressourcenabfolge an:
Wie erwartet dauerte der Download der HTML-Datei etwa 200 ms. Beachten Sie, dass der transparente Teil der blauen Linie die Zeit angibt, die der Browser im Netzwerk wartet, ohne Antwort-Byte zu empfangen, während der durchgezogene Teil die Zeit anzeigt, die für den Abschluss des Downloads nach Erhalt der ersten Antwort-Byte benötigt wird. Der HTML-Download ist sehr klein (< 4K), sodass wir nur einen einzigen Roundtrip benötigen, um die vollständige Datei abzurufen. Daher dauert das Abrufen des HTML-Dokuments etwa 200 ms, wobei die Hälfte der Zeit im Netzwerk und die andere Hälfte auf die Serverantwort gewartet wurde.
Wenn der HTML-Inhalt verfügbar ist, parst der Browser die Bytes, wandelt sie in Tokens um und erstellt die DOM-Baumstruktur. In den DevTools wird unten die Zeit für das DOMContentLoaded-Ereignis (216 ms) angezeigt, was auch der blauen vertikalen Linie entspricht. Die Lücke zwischen dem Ende des HTML-Downloads und der blauen vertikalen Linie (DOMContentLoaded) ist die Zeit, die der Browser benötigt, um den DOM-Baum zu erstellen – in diesem Fall nur wenige Millisekunden.
Das Ereignis domContentLoaded
wurde durch unser „tolles Foto“ nicht blockiert. Wir können den Renderbaum erstellen und die Seite sogar darstellen, ohne auf jedes Asset auf der Seite zu warten: Nicht alle Ressourcen sind für eine schnelle First Paint erforderlich. Beim kritischen Rendering-Pfad sprechen wir in der Regel über HTML-Markup, CSS und JavaScript. Das anfängliche Rendern der Seite wird durch Bilder nicht blockiert. Wir sollten aber auch versuchen, die Bilder so schnell wie möglich zu erstellen.
Das Ereignis load
(auch onload
genannt) ist jedoch im Bild blockiert: In den DevTools wird das Ereignis onload
mit 335 ms angegeben. Das Ereignis onload
markiert den Punkt, an dem alle für die Seite erforderlichen Ressourcen heruntergeladen und verarbeitet wurden. An diesem Punkt kann das Ladesymbol im Browser (die rote vertikale Linie in der Ablaufgrafik) aufhören zu rotieren.
JavaScript und CSS hinzufügen
Unser „Hello World“-Erlebnis auf eine Seite zu beschränken, aber im Hintergrund ist vieles passiert. In der Praxis benötigen wir mehr als nur HTML: Die Chancen stehen gut, dass wir über ein CSS-Style-Sheet und ein oder mehrere Skripts verfügen, um unsere Seite interaktiv zu gestalten. Probieren Sie es aus:
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Script</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body onload="measureCRP()">
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="timing.js"></script>
</body>
</html>
Vor dem Hinzufügen von JavaScript und CSS:
Mit JavaScript und CSS:
Durch das Hinzufügen externer CSS- und JavaScript-Dateien werden unserer Abfolge zwei zusätzliche Anfragen hinzugefügt, die alle ungefähr gleichzeitig vom Browser gesendet werden. Beachten Sie jedoch, dass die Zeitdifferenz zwischen den Ereignissen domContentLoaded
und onload
jetzt viel geringer ist.
Was ist passiert?
- Im Gegensatz zu unserem einfachen HTML-Beispiel müssen wir zum Erstellen des CSSOM auch die CSS-Datei abrufen und parsen. Zum Erstellen der Rendering-Struktur benötigen wir sowohl das DOM als auch das CSSOM.
- Da die Seite auch einen Parser enthält, der eine JavaScript-Datei blockiert, wird das
domContentLoaded
-Ereignis blockiert, bis die CSS-Datei heruntergeladen und geparst wird: Da der JavaScript-Code möglicherweise das CSSOM abfragt, müssen wir die CSS-Datei blockieren, bis sie heruntergeladen ist. Erst dann kann JavaScript ausgeführt werden.
Was passiert, wenn wir das externe Script durch ein Inline-Script ersetzen? Selbst wenn das Skript direkt in die Seite eingebunden ist, kann der Browser es erst ausführen, wenn das CSSOM erstellt wurde. Kurz gesagt: Inline-JavaScript ist auch Parserblockierung.
Wird die Seite durch das Einfügen des Scripts trotz CSS-Blockierung schneller gerendert? Probieren Sie es aus und sehen Sie, was passiert.
Externes JavaScript:
Inline-JavaScript:
Wir stellen eine Anfrage weniger, aber sowohl die onload
- als auch die domContentLoaded
-Zeiten sind praktisch gleich. Warum? Wir wissen, dass es keine Rolle spielt, ob der JavaScript-Code inline oder extern ist, denn sobald der Browser das Skript-Tag erreicht, blockiert er und wartet, bis das CSSOM erstellt ist. Außerdem lädt der Browser in unserem ersten Beispiel sowohl CSS als auch JavaScript parallel herunter und der Download ist ungefähr gleichzeitig abgeschlossen. In diesem Fall bringt das Einfügen des JavaScript-Codes nicht viel. Es gibt jedoch mehrere Strategien, mit denen sich das Rendern der Seite beschleunigen lässt.
Denken Sie daran, dass alle Inline-Skripts den Parser blockieren. Für externe Skripts können wir das Attribut async
hinzufügen, um die Blockierung des Parsers aufzuheben. Heben Sie das Inline-Einfügen auf und versuchen Sie es noch einmal:
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Async</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body onload="measureCRP()">
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script async src="timing.js"></script>
</body>
</html>
Parser-blockierendes (externes) JavaScript:
Asynchrones (externes) JavaScript:
Viel besser! Das Ereignis domContentLoaded
wird kurz nach dem Parsen der HTML-Datei ausgelöst. Der Browser weiß, dass JavaScript nicht blockiert werden darf. Da es keine anderen Scripts gibt, die den Parser blockieren, kann die CSSOM-Konstruktion auch parallel erfolgen.
Alternativ hätten wir sowohl das CSS als auch das JavaScript einfügen können:
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Inlined</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
p {
font-weight: bold;
}
span {
color: red;
}
p span {
display: none;
}
img {
float: right;
}
</style>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script>
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
</script>
</body>
</html>
Beachten Sie, dass die domContentLoaded
-Zeit effektiv mit der im vorherigen Beispiel identisch ist. anstatt unser JavaScript als asynchron zu kennzeichnen, haben wir sowohl CSS als auch JS in die Seite selbst eingefügt. Dadurch wird unsere HTML-Seite viel größer, aber der Vorteil ist, dass der Browser nicht warten muss, um externe Ressourcen abzurufen. Alles ist direkt auf der Seite.
Wie Sie sehen können, ist das Optimieren des kritischen Rendering-Pfads selbst bei einer sehr einfachen Seite eine nicht triviale Aufgabe: Wir müssen das Abhängigkeitsdiagramm zwischen verschiedenen Ressourcen verstehen und ermitteln, welche Ressourcen „kritisch“ sind. und es müssen verschiedene Strategien zur Einbindung dieser Ressourcen auf der Seite ausgewählt werden. Für dieses Problem gibt es keine einzige Lösung. jede Seite ist anders. Sie müssen einen ähnlichen Prozess für sich selbst durchlaufen, um die optimale Strategie zu finden.
Sehen wir uns an, ob wir einige allgemeine Leistungsmuster identifizieren können.
Leistungsmuster
Die einfachste mögliche Seite besteht lediglich aus dem HTML-Markup. kein CSS, kein JavaScript oder andere Arten von Ressourcen. Um diese Seite zu rendern, muss der Browser die Anfrage initiieren, auf das Eintreffen des HTML-Dokuments warten, es parsen, das DOM erstellen und es schließlich auf dem Bildschirm rendern:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Critical Path: No Style</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
Die Zeit zwischen T0 und T1 erfasst die Netzwerk- und Serververarbeitungszeiten. Im günstigsten Fall (wenn die HTML-Datei klein ist) ruft nur ein Netzwerk-Roundtrip das gesamte Dokument ab. Aufgrund der Funktionsweise der TCP-Transportprotokolle können bei größeren Dateien mehr Umläufe erforderlich sein. Im besten Fall hat die obige Seite also im besten Fall einen kritischen Rendering-Pfad mit einem Umlauf (Minimum).
Sehen Sie sich nun dieselbe Seite mit einer externen CSS-Datei an:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
Noch einmal müssen wir einen Netzwerk-Roundtrip ausführen, um das HTML-Dokument abzurufen. Das abgerufene Markup gibt uns dann an, dass wir auch die CSS-Datei benötigen. Das bedeutet, dass der Browser zum Server zurückkehren und das CSS abrufen muss, bevor er die Seite auf dem Bildschirm rendern kann. Daher sind für diese Seite mindestens zwei Hin- und Rückwege erforderlich, bevor sie angezeigt werden kann. Noch einmal: Die CSS-Datei kann mehrere Rundreisen erfordern, daher der Hinweis auf „minimal“.
Im Folgenden finden Sie einige Begriffe, die wir für den kritischen Rendering-Pfad verwenden:
- Kritische Ressource: Ressource, die das erste Rendering der Seite blockieren könnte.
- Kritische Pfadlänge: Die Anzahl der Umläufe oder die Gesamtzeit, die zum Abrufen aller kritischen Ressourcen erforderlich ist.
- Kritische Byte: Die Gesamtzahl der Byte, die für das erste Rendern der Seite erforderlich sind. Dies ist die Summe der Übertragungsdateigrößen aller kritischen Ressourcen. Unser erstes Beispiel mit einer einzelnen HTML-Seite enthielt eine einzige kritische Ressource (das HTML-Dokument). Die Länge des kritischen Pfads entsprach ebenfalls einem Netzwerk-Roundtrip (vorausgesetzt, die Datei war klein) und die Gesamtzahl der kritischen Bytes entsprach nur der Übertragungsgröße des HTML-Dokuments selbst.
Vergleichen Sie dies nun mit den Eigenschaften des kritischen Pfads aus dem vorherigen HTML- und CSS-Beispiel:
- 2 kritische Ressourcen
- 2 oder mehr Roundtrips für die Mindestlänge des kritischen Pfads
- 9 KB kritische Byte
Wir benötigen sowohl HTML als auch CSS, um den Renderbaum zu erstellen. Daher sind sowohl HTML als auch CSS wichtige Ressourcen: Das CSS wird erst abgerufen, nachdem der Browser das HTML-Dokument abgerufen hat. Daher beträgt die Länge des kritischen Pfads mindestens zwei Roundtrips. Beide Ressourcen ergeben insgesamt 9 KB kritische Byte.
Fügen Sie jetzt eine zusätzliche JavaScript-Datei hinzu.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js"></script>
</body>
</html>
Wir haben app.js
hinzugefügt, das sowohl ein externes JavaScript-Asset auf der Seite als auch eine Parser-Blockierungsressource (d. h. eine kritische Ressource) ist. Schlimmer noch: Um die JavaScript-Datei auszuführen, müssen wir das CSSOM blockieren und darauf warten. Wie Sie sich erinnern, kann JavaScript das CSSOM abfragen. Daher wird der Browser angehalten, bis style.css
heruntergeladen und das CSSOM erstellt wurde.
Wenn wir uns jedoch die Netzwerkabfolge ansehen, werden die CSS- und JavaScript-Anfragen gleichzeitig initiiert. ruft der Browser den HTML-Code ab, erkennt beide Ressourcen und initiiert beide Anfragen. Daher hat die im vorherigen Bild gezeigte Seite die folgenden Merkmale des kritischen Pfads:
- 3 kritische Ressourcen
- 2 oder mehr Umläufe für die minimale kritische Pfadlänge
- 11 KB kritische Byte
Wir haben jetzt drei kritische Ressourcen, die zusammen 11 KB kritische Bytes ergeben, aber unsere kritischen Pfadlänge beträgt immer noch zwei Roundtrips, da wir CSS und JavaScript parallel übertragen können. Wenn Sie die Eigenschaften Ihres kritischen Rendering-Pfads ermitteln möchten, müssen Sie die kritischen Ressourcen identifizieren und verstehen, wie der Browser die Abrufe plant.
Nach dem Chat mit unseren Website-Entwicklern stellen wir fest, dass das JavaScript, das wir auf unserer Seite eingefügt haben, nicht blockiert werden muss. Wir haben Analytics und anderen Code,
der das Rendering unserer Seite nicht blockieren muss. Mit diesem Wissen können wir dem <script>
-Element das Attribut async
hinzufügen, um die Blockierung des Parsers aufzuheben:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
Ein asynchrones Script bietet mehrere Vorteile:
- Das Script blockiert den Parser nicht mehr und ist nicht Teil des kritischen Rendering-Pfads.
- Da es keine anderen kritischen Scripts gibt, muss das CSS das
domContentLoaded
-Ereignis nicht blockieren. - Je früher das
domContentLoaded
-Ereignis ausgelöst wird, desto eher kann die andere Anwendungslogik ausgeführt werden.
Daher hat unsere optimierte Seite jetzt wieder zwei kritische Ressourcen (HTML und CSS) mit einer Mindestlänge des kritischen Pfades von zwei Rundreisen und insgesamt 9 KB kritischer Byte.
Wie würde das CSS-Stylesheet aussehen, wenn es nur für den Druck benötigt würde?
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" media="print" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
Da die style.css-Ressource nur für den Druck verwendet wird, muss der Browser sie nicht blockieren, um die Seite zu rendern. Sobald die DOM-Konstruktion abgeschlossen ist, verfügt der Browser über genügend Informationen, um die Seite zu rendern. Daher hat diese Seite nur eine einzige kritische Ressource (das HTML-Dokument) und die minimale Länge des kritischen Rendering-Pfads ist ein Hin- und Rücklauf.