Lazy Loading Images mit Custom Elements

"Ich roll' dann mal aus"  –  247 Kommentare

Ein Blick in den Quellcode von heise online fördert seit einiger Zeit kaum noch <img>-Tags zutage, sondern zumeist <a-img>. Was das ist und wieso wir das machen, wollen wir hier einmal erläutern.

An Bilder im Internet sind die letzten Jahre zunehmend höhere Ansprüche gestellt worden. Genügte jahrelang das einfache <img>-Tag, kamen mit Responsive Layouts und High-DPI-Displays immer mehr Anforderungen, die nur gemächlich Einzug in die Standards hielten. Des Weiteren ist das Web bildreicher geworden. Lange Seiten laden zig Bilddateien herunter, die User eventuell nie sehen. Das Nachladen von Grafiken (Lazy Loading), wenn User sich durch Scrollen den Bildern annähern, dient hier also vor allem der Performance und schont auf Mobilgeräten das Datenvolumen. Für uns als Serverbetreiber ist es ebenso von Vorteil, nur die Daten zu übertragen, die die User auch tatsächlich nutzen.

Web Components

Web Components sind eine Sammlung von Techniken, um wiederverwendbare, in sich geschlossene Komponenten zu erstellen. Kerntechniken dabei sind Custom Elements, Shadow-DOM, HTML imports und HTML Template.

Siehe dazu auf heise Developer:

Custom Elements sind Teil des Web-Components-Standards und noch recht frisch. Da nicht alle Browser ihre Implementierungen abgeschlossen haben, sind aktuell noch Polyfills zur Nutzung nötig, die wir nur den Browsern ausliefern, die das entsprechende Feature noch nicht kennen.

In HTML beziehungsweise in Browsern war es schon immer möglich, <meintag> ins DOM einzufügen und es einfach zu benutzen. Das war jedoch nicht konform mit dem HTML-Standard und konnte zu unterschiedlichen Problemen führen. Custom Elements hingegen ermöglichen es, dem Browser eigene Tags beizubringen. Als Bedingung für die Bezeichnung sticht vor allem heraus, dass das Tag einen Bindestrich haben muss, also zum Beispiel <mein-tag>. Im JavaScript wird dieses Tag dann dem Browser bekannt gemacht und die Funktion beschrieben.

Custom Elements sollen helfen, wiederverwendbare Komponenten zu schreiben; die eigene Tag-Bezeichnung darf hier als Namespacing verstanden werden. Ist ein Custom Element einmal dem Browser bekannt gemacht, kann es wie jedes andere Tag problemlos auch asynchron nachträglich ins DOM eingefügt werden und funktioniert sofort. Darüber hinaus ist bei der Verwendung des optionalen Shadow DOM der Scope immer automatisch auf das aktuelle Custom Element beschränkt, mit dem interagiert wird.

Wieso haben wir nun eigentlich überhaupt ein Bild als Custom Element gelöst? Vorweg: Am Ende landet im DOM auch wieder nur ein altbekanntes <img>, jedoch geschachtelt in unserem Custom Element <a-img>. Das <img> wird allerdings an den aktuellen User angepasst. Die Bildgröße richtet sich am Elternelement aus (meist ein <figure>), das je nach Breakpoint beziehungsweise Gerät des Users (Smartphone, Tablet, Desktop) unterschiedlich groß sein kann. Wir müssen hier nicht mittels Media Query auf alles vorbereitet sein – das erledigt das JavaScript für uns und fordert eine entsprechende Variante des Bildes vom Server an.

Neben der reinen Bildgröße analysiert das JavaScript auch, ob es sich um ein High-DPI-Display handelt, und baut dann eine hochauflösende Variante ein. In Zukunft wäre es auch möglich, die Bandbreite des Users mit einfließen zu lassen ("Network Information API") und bei lahmer Verbindung trotz High-DPI-Display nur eine niedriger aufgelöste Grafik zu senden.

Der Nachteil an Media Queries ist, dass sie sich immer auf die Viewport-Größe beziehen. Wird also ein Bild in einer Randspalte mit nur 200 Pixel Breite angezeigt, greift dieselbe Media Query wie beim großen Aufmacherbild mit 600 Pixel Breite im Artikel. Hier müsste also für jedes etwaige Vorkommen eines Bildes in unterschiedlicher Größe ein eigenes Set an Media Queries geschrieben werden. Da macht es das JavaScript einfacher, das sich am verfügbaren Platz und der tatsächlichen Größe des Bildes orientiert.

Lazy-Loading bei der Arbeit

Neben einem möglichst optimalen Bild, was im Prinzip auch bereits mit <img>, srcset- und sizes-Attributen und dem <picture>-Tag möglich wäre, kann <a-img> aber auch Lazy Loading. Das heißt, Bilder die gar nicht im Viewport des Users sind, werden auch nicht geladen. Bewegt der User das Bild allerdings in Richtung des Viewports (z. B. durch Scrollen) wird es geladen. Dank Custom Element müssen wir hier jedoch keine Verrenkungen machen, wie man es in der Vergangenheit getan hat ("1px.gif", leere src-Attribut etc.). <a-img> kümmert sich darum, dass das <img> erst geladen und eingebunden wird, wenn sich der User tatsächlich in die Nähe des Bildes bewegt – aktuell bei uns eine Bildschirmhöhe des Users. Dafür verwenden wir die Intersection Observer API, ebenfalls eine neue Technik, die es endlich erlaubt solche Operationen ohne EventListener auf das scroll-Event vorzunehmen, das ja immer wieder einige Probleme mit sich gebracht hat.

Intersection Observer sind außerdem asynchron und blockieren somit nicht den Main-Thread. Ein weiteres Feature ist, dass man eine Zone um das zu observierende Element herum definieren kann ("rootMargin"), das eine Aktion auslöst. Konkret bedeutet das, Bilder werden nicht erst geladen, wenn sie auch tatsächlich im Viewport sind, sondern wir können bereits schon vorher – beispielsweise wenn das Bild noch 500 Pixel vom Viewport entfernt ist – das Laden des Bildes initiieren. Damit ist die Wahrscheinlichkeit hoch, dass es bereits fertig geladen ist, wenn der User beim Bild angekommen ist.

Bei Codepen haben wir ein reduziertes Beispiel von <a-img> hinterlegt, das nur die Funktion des Lazy Loading mittels Intersection Observer umfasst. Um das Beispiel einfach zu halten, funktioniert es in der vorliegenden Fassung nur mit aktuellen Browsern, die alle Features bereits von Haus aus unterstützen (beispielsweise der aktuelle Chrome oder Firefox Nightly).

Mit der Integration sind wir aktuell recht zufrieden. Uns ist jedoch bewusst, dass hier viele neue Techniken zusammenkommen, die gerade in älteren Browsern teils nicht funktionieren. Polyfills helfen dabei eine Menge, aber auch nicht immer. Beispielsweise haben wir im IE11 starke Performance-Probleme feststellen müssen, sodass das JavaScript lediglich einmalig ein <img>-Tag ausliefert, das dann leider kein Lazy Loading beherrscht oder auf Display-Veränderungen reagiert. Auch den <noscript>-Fall haben wir nicht außer Acht gelassen, hier wird ebenfalls ein einfaches <img>-Tag ausgegeben.

Wer sich übrigens gefragt hat, weshalb wir "a-" als Präfix gewählt haben: Das beruht auf unserem selbst entwickelten hausinternen Frontend-Framework "akwa", über das wir sicherlich ein anderes Mal noch berichten werden.