Ein optimiertes Frontend für noch mehr Performance: Lazy Loading in TYPO3 10 LTS – Teil 1
Lasst uns in den zwei Teilen meines Blog-Artikels auf eine kleine Reise gehen. Beginnen werden wir bei der simplen Einbindung einer Lazy-Loading-Lösung auf Basis von Javascript. Danach machen wir einen kurzen Zwischenstopp beim loading-Attribut. Und im zweiten Artikel, der schon Mittwoch folgt, verheiraten wir anschließend beide Lösungen und schauen dann, was das Ganze im TYPO3 Umfeld bedeutet. Der Einfachheit halber beschränken wir uns dabei immer nur auf Bilder.
Was ist Lazy Loading?
Bei einer performanten Website sollte der Browser nur die Ressourcen laden, die auch wirklich fürs Anzeigen der Seite gebraucht werden. Zum einen spart es wertvolles Transfervolumen beim mobilen Surfen, zum anderen verhindert es, dass der Browser unnötige Rechenarbeit leisten muss. Beim Lazy Loading sorgt man dafür, dass z. B. Bilder erst dann durch den Browser vom Server angefragt werden, wenn sie auch angezeigt werden sollen. Sehr vereinfacht gesagt: Wenn ich erst ein ganzes Stück nach unten scrollen muss, um zu einem Bild zu gelangen, sollte dies nicht direkt beim Seitenaufruf geladen werden. Stattdessen soll der Browser das Bild erst herunterladen, wenn ich „nah“ herangescrollt habe. Bis vor kurzem wurde das in der Regel durch JavaScript realisiert. Mittlerweile gibt es jedoch die Möglichkeit auf das loading-Attribut als Teil des HTML-Standards zurückzugreifen.
Wie lädt ein Browser Bilder?
Die klassische Einbindung eines Bildes in HTML ist denkbar einfach. Gegeben sei eine unglaublich minimalistische Website:
<html>
<head>
<title>Beispielseite</title>
</head>
<body>
<img src="img1.png" />
</body>
</html>
Rufen wir diese Seite mit einem Browser auf, ist der Quelltext schnell vom Browser eingelesen und er weiß, dass das Bild img1.png dargestellt werden soll. Es wird also vom Server angefragt und heruntergeladen. Damit der Titel der Seite dargestellt werden kann, sind keine weiteren Schritte notwendig. Der Browser kann die Seite anzeigen, sobald das Bild heruntergeladen wurde:
Jetzt definieren wir, dass vor dem Bild etwas Platz gelassen werden soll: 10.000 Pixel Abstand nach oben.
<html>
<head>
<title>Beispielseite</title>
</head>
<body>
<img src="img1.png" style="margin-top: 10000px;"/>
</body>
</html>
Wenngleich der Browser nun direkt nach dem Laden des HTML-Dokuments die Seite anzeigen könnte (die ja einfach nur weiß wäre), lädt er allerdings trotzdem zuerst das Bild herunter:
Klassischerweise würde man hier nun auf ein kleines Javascript setzen, das den Quelltext so umschreibt, dass der Browser das Bild erst später lädt. Lösungen dafür gibt es viele. Allerdings sollte jeder für sich selbst entscheiden, welche die richtige ist. Für dieses Beispiel werden wir auf lazysizes setzen.
Für die Implementierung müssen wir dem Browser die Möglichkeit nehmen, das Bild zu laden. Das machen wir, indem wir die src-Angabe leeren (oder mit einem Platzhalterbild befüllen). Dann schreiben wir den Pfad zum gewünschten Bild ins data-src-Attribut, aus dem uns lazysizes später das src-Attribut generiert. Damit lazysizes weiß, bei welchen Bildern das gemacht werden soll, fügen wir „lazyload“ als class ein. Unser img-Element sieht dann so aus:
<img class=“lazyload“ data-src="img1.png" style="margin-top: 10000px;"/>
Hiermit würde der Browser natürlich nichts anzeigen, da wir lazysizes noch nicht geladen haben. Das geht zum Glück mit einer einzigen Zeile:
<script src="lazysizes.min.js"></script>
Die minifizierte Variante von lazysizes könnt ihr hier herunterladen.
Unser vollständiges Beispiel sieht nun so aus:
<html>
<head>
<title>Beispielseite</title>
</head>
<body>
<img class="lazyload" data-src="img1.png" style="margin-top: 10000px;"/>
<script src="lazysizes.min.js"></script>
</body>
</html>
Das Bild wird erst geladen, wenn wir einen Großteil der 10.000 Pixel nach unten gescrollt haben (man achte auf den Scrollbalken):
So viel zum „klassischen“ Vorgehen. Wenden wir uns nun den neuen Möglichkeiten zu!
Das loading-Attribut
Dass man zwangsweise Javascript einsetzen muss, um Bilder erst dann zu laden, wenn sie wirklich benötigt werden, ist natürlich nicht schön. Zum Glück naht Rettung in Form des loading-Attributs, das von vielen gängigen Browser (Chrome, Firefox, Edge) unterstützt wird – oder zumindest in den Entwicklereinstellungen aktiviert werden kann (Safari / iOS Safari). Eine umfangreiche Liste dazu gibt es hier.
Das loading-Attribut teilt dem Browser ohne zusätzliches Javascript mit, ob ein Element direkt geladen werden soll oder erst wenn es für die Anzeige der Seite benötigt wird. Es kann für Bilder, Video & Audio-Elemente sowie iframes benutzt werden. Neben der Browserunterstützung sollte man die drei Einstellungsmöglichkeiten des loading-Attributs kennen.
- "lazy" definiert, dass das Element „lazy“ geladen werden soll
- "eager" definiert, dass das Element sofort geladen werden soll (bspw. für ein Logo im above-the-fold-Content sinnvoll; diese Einstellung entspricht dem Verhalten, wenn das Attribut gar nicht genutzt wird)
- "auto" definiert, dass der Browser selbst entscheiden soll, ob ein Element lazy geladen wird
(Wird aktuell nur von Chrome unterstützt und ist nicht Teil der offiziellen Spezifikation!)
Fügen wir das Attribut unserem ursprünglichen img-Tag hinzu, haben wir fast schon die Funktion, die uns auch lazysizes bietet:
<html>
<head>
<title>Beispielseite</title>
</head>
<body>
<img loading="lazy" src="img1.png" style="margin-top: 10000px;"/>
</body>
</html>
Das Bild wird beim Aufruf nicht direkt geladen, sondern erst nachdem wir etwas gescrollt haben:
Warum nur fast?
Mit ein paar Einschränkungen müssen wir leider leben, wenn wir ausschließlich das loading-Attribut nutzen:
- Nutzer von Browser wie Safari, die es noch nicht unterstützen, kommen jetzt nicht in den Genuss des Lazy Loadings.
- Die Entscheidung, wann das Bild geladen wird, liegt nur noch beim Browser. Wir können es nicht mehr selbst steuern (was wir per Javascript grundsätzlich könnten).
Mit der Zeit wird sich der erste Faktor weitestgehend von selbst lösen. Sobald die Unterstützung standardmäßig in Safari aktiv ist, ist die Abdeckung auf dem Browser-Markt schon sehr groß. (Irgendwann werden hoffentlich auch die Nutzer alter Chrome- und Firefox-Versionen ein Update machen und der Internet Explorer zugunsten von Edge aussterben. ;-))
Natürlich können wir auch eine Hybrid-Lösung nutzen, um alle Browser mit dem Lazy Loading zu versorgen (mehr dazu im zweiten Teil meines Artikels, der am Mittwoch erscheint).
Darüber hinaus ist der zweite Punkt wichtig. Denn im Moment verarbeiten die Browser das loading-Attribut keineswegs gleich. Firefox lädt die Bilder z. B. erst, wenn man schon sehr nah am visible viewport ist. Das hat den Vorteil, dass sehr wenige Daten übertragen werden.
Einen anderen Ansatz verfolgt Chrome. Hier werden die Bilder schon sehr früh geladen und das Lazy-Loading damit auf „kleinen“ Seiten sogar oft ganz ausgehebelt. Chrome entscheidet über den Ladezeitpunkt nämlich u. a. anhand der Verbindungsgeschwindigkeit des Benutzers. Je langsamer die Verbindung ist, umso früher wird ein Bild geladen. Damit werden tendenziell häufiger Bilder geladen, die der Websitebesucher vielleicht gar nicht sieht. Gleichsam wird verhindert, dass ein Benutzer mit einer langsamen Verbindung ewig auf Bilder warten muss. Wenn ich zum Beispiel erst den Text am Anfang einer Seite lese, nur langsam nach unten scrolle und „plötzlich“ auf ein Bild stoße. Chrome wird dieses Bild schon geladen haben, während es im Firefox erst jetzt angefordert wird. Scrolle ich aber nicht runter, ist Firefox im Vorteil, da das Bild nicht unnötigerweise angefordert wird.
Wer es etwas konkreter wissen will, kann sich hier anschauen, wie genau Chrome bei den einzelnen Verbindungsgeschwindigkeiten reagiert. Wichtig dabei: Es handelt sich hierbei um eine recht frische Implementierung. Ich vermute, dass sich diese Werte noch verändern werden, wenn das loading-Attribut weitläufiger eingesetzt wird. Dann kann Chrome mit Daten von echten Websites ermitteln, wie sich die Nutzer verhalten und die Werte entsprechend anpassen. Das gilt natürlich auch für andere Browser.
Wer unabhängig davon auf das loading-Attribut setzen möchte und die Websitebesucher, deren Browser noch keine Unterstützung anbieten, nicht im Regen stehen lassen will, kann auf eine Hybrid-Lösung setzen...
Fortsetzung folgt.
Teil 2 folgt – Vorschau
Das war es fürs Erste. Am Mittwoch werdet ihr dann erfahren, wie wir die beiden Welten verheiraten und ebendiese Hybrid-Lösung schaffen. Und ich bin es euch noch schuldig, zu zeigen, was das Ganze im TYPO3 Umfeld bedeutet. Seid gespannt und habt einen guten Start in die Woche!