Schlanke, responsive Bilder — mit MozJPEG, WebP, AVIF & SQIP

Veröffentlicht als Einblick
von Joschi Kuphalam

In dieser Making Of-Artikelreihe führen wir hinter die Kulisse unserer neuen Website, in den Maschinen­raum sozusagen. Wir erläutern Ansätze und Ziele, plaudern aus dem Technik-Nähkistchen und halten fest, was wir unterwegs gelernt haben.

Seit dem Relaunch unserer Website im März 2020 nutzen wir an vielen Stellen responsive Bilder, um den unterschiedlichen Endgeräten — Smartphones, Tablets, Desktop- und anderen Computern — angepasste Bildabmessungen und spezialisierte Dateiformate auszuliefern. Dass Chrome als erster Browser nun das neue AVIF-Bildformat unterstützt, nehmen wir zum Anlass für einen genaueren Blick unter die Haube.

Bilder sind bekanntlich für einen Großteil des Internet-Traffics verantwortlich — laut HTTP Archive machen sie derzeit etwa 50% der durchschnittlichen Website aus. Traffic kostet Ressourcen, und nicht zuletzt unsere Zeit und Geduld. Es macht deshalb Sinn, sich intensiv mit Bildern auseinanderzusetzen und dafür zu sorgen, dass sie so kompakt und bedarfsgerecht wie möglich übertragen werden.

Mit dem HTML-Element <picture> steht eine vielseitige Technik zur Verfügung, um unterschiedlichsten Szenarien gerecht zu werden. Wir zeigen, wie wir das <picture>-Element auf der Tollwerk-Website einsetzen und damit gleich mehrere Aufgaben lösen. Der Einsatz des <picture>-Elements kann schnell komplex werden — wir beschränken uns hier jedoch bewusst auf die einsteigerfreundlichen Grundlagen.

Optimale Dimensionen

So vielfältig wie die Auswahl an internetfähigen Geräten ist, so unterschiedlich sind auch die Bildschirme, auf denen eine Website potenziell angezeigt wird. Dabei macht es grundsätzlich keinen Sinn, einem Gerät ein Bild zur Verfügung zu stellen, das in seinen Abmessungen größer ist, als es auf dem Gerät dargestellt wird: Teile der übertragenen Daten blieben sonst ungenutzt, die Bandbreite wäre verschwendet.

Unrealistisch ist es aber auch, für jede denkbare Darstellung ein genau passendes Bild bereitzuhalten. Als Kompromiss kann man deshalb beim Entwickeln einer Website bestimmte Schwellenwerte festlegen, die sogenannten Breakpoints, und nur für diese angepasste Bildvarianten vorbereiten. Für unsere Website nutzen wir beispielsweise die Breakpoints 512, 672 und 1024 Pixel und begrenzen die Maximalbreite von Bildern zusätzlich auf 1200 Pixel. Der folgende Code zeigt in vereinfachter Form, wie unsere Kopfbilder auf der Startseite an die Fensterbreite definiert sind:

<picture>
    <source srcset="team-xl.jpg 1200w" media="(min-width: 1025px)" sizes="(min-width: 1200px) 1200px, 100vw">
    <source srcset="team-l.jpg 1024w" media="(min-width: 673px) and (max-width: 1024px)" sizes="100vw">
    <source srcset="team-m.jpg 672w" media="(min-width: 513px) and (max-width: 672px)" sizes="100vw">
    <source srcset="team-s.jpg 512w" media="(max-width: 512px)" sizes="100vw">

    <img src="team-xl.jpg" width="1200" height="600" alt="Tollwerk-Team im Februar 2020">
</picture>

Zunächst ist zu bemerken, dass im Kern der Bildumsetzung ein konventionelles <img>-Element als Standardbild hinterliegt. Ältere Browser, die das <picture>-Element nicht unterstützen, ignorieren sowohl <picture>, als auch <source> und fallen auf das Standardbild zurück. In dem Fall kann kein Rückschluss auf die Darstellungsgröße gezogen werden und es muss die maximal große Bildvariante an den Browser übertragen werden, um zuverlässig für eine ansehnliche Darstellung zu sorgen. Das Standardbild ist zudem mit einem Alternativtext (alt-Attribut) versehen, so dass Screenreadern und anderen assistiven Technologien ein barrierefreier Umgang mit dem Bild möglich ist.

Für modernere Browser definieren die vier <source>-Elemente unterschiedliche Bildvarianten und knüpfen diese über das media-Attribut an eine Bedingung, die erfüllt sein muss, damit die jeweilige Variante zum Einsatz kommt. Die Schreibweise der Bedingungen entspricht den Media Expressions bei CSS Media Queries: Im Beispiel sind es bestimmte Mindest- und / oder Höchstbreiten, die das Browserfenster einhalten muss. Um zu entscheiden, welche Bildvariante dargestellt werden soll, durchläuft der Browser die <source>-Elemente ihrer Reihenfolge nach und wählt die erste, deren Bedingung erfüllt ist. Das sizes-Attribut der gewählten Variante steuert schließlich die genaue Darstellung: Im Beispiel nimmt das Bild stets 100% der verfügbaren Breite ein (100vw), keinesfalls aber mehr als 1200 Pixel.

Durch Einsatz des <picture>-Elements mit unterschiedlich großen Bildvarianten entscheidet also der Browser, welches das optimale Bild für die aktuelle Darstellung ist, und nur dieses Bild wird vom Server übertragen. Unnötige Übertragung zu großer Datenmengen wird vermieden.

Optimale Motive

Abgesehen von bedarfsgerechten Bilddimensionen hilft uns die oben dargestellte Technik auch bei einem ganz anderen Thema: dem Art Direction Problem. Dieser Anwendungsfall bezieht sich darauf, dass bei variierenden Bildschirmgrößen oft nicht nur die Bilddimensionen angepasst werden müssen. In manchen Fällen bietet es sich beispielsweise auch an, die Bildausschnitte im Motiv zu verändern, das Seitenverhältnis zwischen Quer- und Hochformat zu wechseln, oder gar ein vollkommen anderes Motiv zum Einsatz zu bringen. Genau das erlaubt uns das <picture> -Element mit seinen mehreren Bildquellen für die einzelnen Breakpoints:

<picture>
    <source srcset="team-2-zu-1.jpg 1200w" media="(min-width: 1025px)" sizes="(min-width: 1200px) 1200px, 100vw">
    <source srcset="team-4-zu-3.jpg 1024w" media="(min-width: 673px) and (max-width: 1024px)" sizes="100vw">
    <source srcset="team-1-zu-1.jpg 672w" media="(min-width: 513px) and (max-width: 672px)" sizes="100vw">
    <source srcset="team-3-zu-4.jpg 512w" media="(max-width: 512px)" sizes="100vw">

    <img src="team-2-zu-1.jpg" width="1200" height="600" alt="Tollwerk-Team im Februar 2020">
</picture>

Für die Kopfbilder auf unserer Startseite nutzen wir 4 unterschiedliche Seitenverhältnisse — 2:1, 4:3, 1:1 und 3:4 —, die je nach Fensterbreite zum Einsatz kommen. Auf Smartphone-Bildschirmen in Portrait-Ausrichtung wird ein hochformatiger Bildausschnitt gezeigt, während breite Bildschirme ein Querformat darstellen. Zusätzlich zum Seitenverhältnis unterscheiden sich die Bilder auch im Bildausschnitt.

Zur Ausschnitte nutzen wir den in TYPO3 integrierten Bildeditor und legen in einem annähernd quadratischen, zentral im System hinterlegten Originalbild die vier erforderlichen Ausschnitte fest (sogenannte Crop Variants). TYPO3 nutzt diese Angaben, um daraus automatisch die entsprechenden Einzelbilder zu erzeugen.

Screenshot des TYPO3-Bildeditors, in dem ein Rechteck den aktuellen Ausschnitt eines Fotos markiert
Multiple Bildausschnitte

Mit Hilfe des TYPO3-Bildeditors lassen sich bequem verschiedene Bildausschnitte mit vordefinierten Seitenverhältnissen wählen.

Quelle: Joschi Kuphal / tollwerk, , Alle Rechte vorbehalten

Das <picture>-Element hilft uns also weiterhin, indem es uns bedarfsgerechte Bildausschnitte ermöglicht — hier mehr ein inhaltlich-ästhetischer, denn ein technisch-funktionaler Gewinn.

Optimale Formate

Für die Dateigröße eines Bildes — und damit für die Menge der zu übertragenden Daten — sind jedoch nicht allein die Bilddimensionen verantwortlich. Eine maßgebliche Rolle spielt vor allem auch das Bild- beziehungsweise Dateiformat, in dem die Abbildung zur Verfügung gestellt wird. In Abhängigkeit vom Format stehen unterschiedliche Kompressionsmethoden zur Verfügung, die zum Teil erhebliche Größeneinsparungen ermöglichen. Die folgende Übersicht skizziert die wesentlichen, für die Darstellung im Internet gebräuchlichen Dateiformate und charakterisiert ihre typischen Einsatzgebiete.

  • Das traditionelle JPEG-Format hat nach wie vor die breiteste Unterstützung bei Endgeräten und Anwendungen und eignet sich vor allem für fotografische Darstellungen mit vielen Farben und Verläufen. Es unterstützt keine Transparenz und ist durch eine variable, verlustbehaftete Kompressionsmethode gekennzeichnet, die je nach Qualitätseinstellung zu blockartigen Vergröberungen im Bild, den sogenannten Artefakten, führen kann. Mit dem MozJPEG-Encoder steht jedoch eine fortschrittliche Technik zur Verfügung, mit der sich bestmögliche Ergebnisse aus JPEG-Dateien herausholen lassen.
  • Das GIF-Format ist auf die Darstellung von 256 Farben begrenzt und eignet sich daher nur eingeschränkt für bestimmte Motive. Dieser Umstand und vor allem lizenzrechtliche Gründe haben dafür gesorgt, dass sich das Format nie auf breiter Basis durchsetzen konnte. Seine Popularität bezog es vielmehr vor allem aus der Unterstützung für Transparenz und der Möglichkeit, Animationen zu enthalten. Durch die Reduktion der Farbpalette auf 256 sind im Vergleich zu JPEG kleinere Dateigrößen möglich, jedenfalls bei geringen Bilddimensionen, die Kompression ist also in der Regel verlustbehaftet. Heute wurde das Format weitestgehend durch PNGs verdrängt.
  • Wenn auch nicht aus technischer Sicht, so gilt doch das PNG-Format in gewisser Weise als GIF-Nachfolger. Lizenzrechtlich unbelastet kombiniert es die Vorteile von JPEGs und GIFs und ermöglicht große Farbpaletten bei gleichzeitiger Unterstützung von Transparenzen im Bild. Die Kompression ist verlustfrei, was insbesondere bei großen Farbpaletten im Bild dazu führt, dass die resultierenden Bilddateien erheblich größer, aber qualitativ hochwertiger sein können als ihre JPEG-Pendants. PNG ist daher weniger geeignet für fotografische Darstellungen. Durch eine bewusste Reduktion der Farbpalette und das Speichern mit nur 8 Bit Farbtiefe lassen sich jedoch gerade bei Icons und einfachen Grafiken hervorragende Ergebnisse erzielen.
  • Das vergleichsweise junge WebP-Format leitet sich ursprünglich aus einem Codec ab, der für die Kompression von Videodateien entwickelt wurde. Es unterstützt verlustfreie und verlustbehaftete Kompression, große Farbpaletten, Transparenz und Animationen. Es darf insofern allen zuvor genannten Formaten als überlegen gelten, auch mit Blick auf die erzielten Größeneinsparungen durch Kompression. Allerdings hat die Verbreitung bei Geräten und Anwendungen noch nicht die von JPEG und PNG erreicht. Auch liegt der Zeitbedarf für die Komprimierung einzelner Bilder deutlich über dem von JPEG und PNG.
  • Das brandneue AVIF-Format leitet sich aus dem AV1-Videoformat ab und könnte langfristig als Ablösung für JPEG dienen. Es bietet einen ähnlichen Funktionsumfang wie WebP, übertrifft dieses jedoch in der Bildqualität bei gleichzeitig kleineren Dateigrößen. Das Kodieren hochlauflösender Bilder für AVIF kostet mit den bislang verfügbaren Encoder-Anwendungen nochmal einiges mehr an Zeit und Rechenkapazität. Seit kurzem unterstützt Chrome als erster Browser nativ die Anzeige von AVIF-Bildern (Stand September 2020).
  • Mit dem SVG-Format steht bereits seit rund 20 Jahren ein vektorbasiertes Format für Grafiken im Web zur Verfügung, das aber nur langsam Verbreitung gefunden hat. Aufgrund seiner Natur eignet es sich besonders für flächen- und linienbasierte Zeichnungen, etwa Icons und Diagramme, und eher kaum für fotografische Inhalte. SVG-Grafiken können verlustfrei skaliert werden, sie sind also nicht auf bestimmte Darstellungsdimensionen beschränkt. Intern speichert SVG die Vektorinformationen als reinen Text, weshalb sich für SVG-Grafiken die hocheffektiven Text-Kompressionsalgorithmen wie Deflate oder gzip verwenden lassen. SVG-Grafiken können zudem mit CSS gestaltet und / oder mit JavaScript animiert werden. Unsere Grafik zu Inklusivem Design etwa nutzt das Prinzip des Progressive Enhancements und ist allein per CSS animiert.

Auf unserer Website setzen wir die oben aufgeführten Formate je nach Bildmotiv und Anwendungsbereich sehr bewusst ein (wir verzichten auf die Verwendung des GIF-Formats). Für fotografische Motive, für die vor allem die Formate JPEG, WebP und — jetzt ganz neu — AVIF in Frage kommen, lassen wir wiederum das <picture>-Element ein universelles Paket schnüren:

<picture>
    <source srcset="team.avif" type="image/avif">
    <source srcset="team.webp" type="image/webp">

    <img src="team.jpg" alt="Tollwerk-Team im Februar 2020">
</picture>

Bei dieser Einsatzvariante stellen wir über die <source>-Elemente unterschiedliche Bildformate desselben Motivs zur Verfügung und zeigen das jeweilige Format über ein zusätzliches type-Attribut an. Die Reihenfolge ist dabei entscheidend: Browser wählen das erste unterstützte Format und laden nur dieses vom Server. Das in der Dateigröße kleinste Format, AVIF, steht deshalb an erster Stelle, gefolgt von WebP und schließlich JPEG als ultimativem Rückfall-Format für Browser, die weder AVIF noch WebP unterstützen. Die so realisierte Formatweiche lässt sich problemlos mit den oben dargestellten Techniken für unterschiedliche Bilddimensionen und -ausschnitte kombinieren.

Laden bei Bedarf

Die bislang erläuterten Techniken nutzen ausschließlich das <picture>-Element, also reine HTML-Auszeichnung, für die Bereitstellung bedarfsgerechter Bilddimensionen, -ausschnitte und -formate. Selbst Browser, die das <picture>-Element oder eines der verwendeten Bildformate nicht unterstützen, werden stets mindestens das enthaltene Rückfallbild darstellen — ohne dabei auf zusätzliche Technologien angewiesen zu sein. Für die letzte Technik, die wir auf unserer Website einsetzen, sind allerdings weitere Technologien notwendig: Bilder sollen im Bestfall nur dann vom Server geladen werden, wenn sie auch wirklich betrachtet werden. Liegt ein Bild außerhalb des sichtbaren Bildschirmbereichs, und scrollen Nutzende niemals dorthin, warum sollte das Bild dann überhaupt vom Server übertragen werden?

Die Technik des Ladens bei Bedarf, auch Lazy Loading genannt, wird für Bilder seit kurzem von manchen Browsern nativ unterstützt. Dazu muss dem Bild lediglich ein loading-Attribut gegeben werden:

<img src="team.jpg" loading="lazy" alt="Tollwerk-Team im Februar 2020">

Der Browser kümmert sich dann selbständig darum, dass ein derart markiertes Bild erst dann vom Server geladen wird, wenn es bereits im sichtbaren Bildschirmbereich liegt oder sich diesem dicht genug nähert. Auch auf diese Weise wird die initiale Datenübertragung auf ein Minimum beschränkt.

Möchte man denselben Effekt auch in Browsern realisieren, die natives Lazy Loading noch nicht unterstützen, muss man sich einer Technik bedienen, die auf JavaScript angewiesen ist. Dabei übernimmt ein Script die Überwachung des sichtbaren Bereichs und das automatische Nachladen von Bildern im Hintergrund, sobald diese kurz vor der Anzeige stehen. Damit die Bilder nicht schon vorzeitig vom Browser geladen werden, wird das src-Attribut des Bildes initial maskiert (zum Beispiel in data-src geändert) und erst bei Bedarf von JavaScript umgeschrieben:

<img data-src="team.jpg" alt="Tollwerk-Team im Februar 2020">

Wir verzichten an dieser Stelle auf eine detaillierte Erläuterung des JavaScripts, das für unsere Webseite unter der Haube aktiv ist. Nur so viel: Wir nutzen die etablierte lazysizes-Bibliothek, die das Überwachen des Bildschirmbereichs und das Umschreiben des data-src-Attributs zu src übernimmt. Zuvor entscheidet ein von uns selbst entwickeltes Script, ob der Browser bereits natives Lazy Loading unterstützt. In dem Fall kann das data-src-Attribut umgehend umgeschrieben werden und die lazysizes-Bibliothek wird nicht benötigt. Im Quelltext nutzen wir daher die Kombination beider Welten:

<img data-src="team.jpg" loading="lazy" alt="Tollwerk-Team im Februar 2020">

Doch was passiert, wenn JavaScript einmal nicht zur Verfügung steht? Im Sinne des Progressive Enhancements haben wir für diesen Fall Vorkehrungen getroffen (stark verkürzt dargestellt):

<html class="no-js">
    <head>
        <style>
            .no-js [data-src][loading="lazy"] {
                display: none;
            }
        </style>
    </head>
	<body>
		<img data-src="team.jpg" loading="lazy" alt="Tollwerk-Team im Februar 2020">
        <noscript>
        	<img src="team.jpg" alt="Tollwerk-Team im Februar 2020">
        </noscript>
    </body>
</html>

Im Auslieferungszustand trägt das <html>-Element unserer Webseite die CSS-Klasse no-js, die in Kombination mit einer einfachen CSS-Regel dafür sorgt, dass alle Bilder, die mit loading="lazy" data-src="..." gekennzeichnet sind, generell ausgeblendet werden. Das CSS ist bewusst inline in den Kopfbereich der Seite integriert, um nicht eine zusätzliche Abhängigkeit zu einem externen CSS einzuführen. Jedes nachzuladende Bild wird im Quelltext durch eine normal zu ladende Variante ergänzt, die in einen <noscript>-Bereich eingeschlossen ist und demnach nur dann greift, wenn JavaScript nicht zur Verfügung steht. Nutzende ohne JavaScript laden also das reguläre Bild und bekommen von der nachzuladenden Variante nichts zu sehen. Steht im Browser jedoch JavaScript zur Verfügung, so wird als eine der ersten Maßnahmen die CSS-Klasse no-js zu has-js geändert. Das Inline-CSS greift nicht mehr, das nachzuladende Bild wird dargestellt und die JavaScript-freie Rückfall-Variante wird nicht genutzt.

Wer genau Acht gegeben hat, der hat in unserem letzten Beispiel ein Detail entdeckt, das im Allgemeinen nicht zulässig ist: Unserem nachzuladenden Bild fehlt das mandatorische src-Attribut! Wir schließen diese Lücke, indem wir eine letzte Verfeinerung integrieren und unserem Bild eine — in der Dateigröße sehr kleine — Vorschauvariante spendieren. Nutzende bekommen diese nur zu Gesicht, solange das nachzuladende Bild noch nicht vollständig übertragen wurde. Dazu wäre es möglich, eine sehr niedrig aufgelöste Version des Bildes zu nutzen:

<img src="team-vorschau.jpg" data-src="team.jpg" loading="lazy" alt="Tollwerk-Team im Februar 2020">

Jedoch würden wir damit den Zweck der ganzen Übung verfehlen, denn dann würde ja doch sofort eine Datei vom Server abgerufen werden müssen. Um diesen expliziten Ladevorgang zu vermeiden, ist es möglich, das Vorschaubild mit Hilfe eines sogenannten Data-URL direkt in den HTML-Quelltext einzulagern:

<img src="data:image/jpg;base64,iVBORw0KGgo..." data-src="team.jpg" loading="lazy" alt="Tollwerk-Team im Februar 2020">

Das Vorschaubild steht damit unmittelbar zusammen mit dem HTML-Dokument zur Verfügung und muss nicht mit einem zusätzlichen Request vom Server übertragen werden. Statt eines volumenmäßig vergleichsweise großen JPEGs nutzen wir für das Vorschaubild eine Technik, die erstmals 2017 von Tobias Baldauf unter der Bezeichnung SQIP vorgestellt wurde: Wir erzeugen von unserem Originalbild eine sehr abstrakte Version als vektorbasierte SVG-Grafik, die mit Weichzeichnungsfiltern so stark verfremdet wird, dass sie nur einen äußerst groben Eindruck vom Originalbild wiedergibt. Das Ergebnis ist in der Dateigröße so klein und kann zusammen mit dem HTML so effizient komprimiert werden, dass eine Übertragung als Inline-Bild realistisch ist.

Stark weichgezeichnete SVG-Grafik, die nur grobe Schemen und eine Farbstimmung zeigt
Abstrakte Vorschau

Durch die starke Abstraktion sind in der Vektorvorschau nur grobe Schemen und eine Farbstimmung zu erahnen

Quelle: Joschi Kuphal / tollwerk GmbH, , Alle Rechte vorbehalten

Das hier dargestellte Vorschaubild ist gerade einmal 1,37 kB groß (unkomprimiert) und stimmt dennoch überzeugend auf das eigentliche Originalbild ein:

Gruppenfoto vor dem Sommerhaus: Joschi, Nina, Annika und Jeff mit Kapuzen und Caps
Ausdrucksstarke Originale

Sobald ein nachzuladendes Bild in den sichtbaren Bereich des Bildschirms gelangt, wird das Vorschaubild gegen das Original ausgetauscht

Quelle: Joschi Kuphal / tollwerk GmbH, , Alle Rechte vorbehalten

Zur Vereinfachung haben wir die Techniken zum Laden bei Bedarf mit einem einzelnen Bild statt einem komplexen <picture>-Element erläutert. Dass Lazy Loading aber auch mit den Methoden für unterschiedliche Bilddimensionen, -ausschnitte und -formate kombiniert werden kann, zeigt das unten stehende, abschließende Beispiel.

Automatische Erzeugung

Alle oben erläuterten Techniken lassen sich mit ein wenig Vorüberlegung gut miteinander kombinieren, wobei der notwendige Quelltext schnell sehr komplex und unübersichtlich werden kann. Das folgende Beispiel zeigt den initialen HTML-Code einer voll ausgebauten Variante der oben stehenden Abbildung unseres Sommerhauses in Island (lediglich die Dateipfade wurden aus Übersichtsgründen gekürzt):

<picture>
    <source data-srcset="island-xl.avif 1200w" media="(min-width: 1025px)" sizes="(min-width: 1200px) 1200px, 100vw" type="image/avif"/>
    <source data-srcset="island-xl.webp 1200w" media="(min-width: 1025px)" sizes="(min-width: 1200px) 1200px, 100vw" type="image/webp"/>
    <source data-srcset="island-l.avif 1024w" media="(min-width: 673px) and (max-width: 1024px)" sizes="(min-width: 1200px) 1200px, 100vw" type="image/avif"/>
    <source data-srcset="island-l.webp 1024w" media="(min-width: 673px) and (max-width: 1024px)" sizes="(min-width: 1200px) 1200px, 100vw" type="image/webp"/>
    <source data-srcset="island-m.avif 672w" media="(min-width: 513px) and (max-width: 672px)" sizes="(min-width: 1200px) 1200px, 100vw" type="image/avif"/>
    <source data-srcset="island-m.webp 672w" media="(min-width: 513px) and (max-width: 672px)" sizes="(min-width: 1200px) 1200px, 100vw" type="image/webp"/>
    <source data-srcset="island-s.avif 512w" media="(max-width: 512px)" sizes="100vw" type="image/avif"/>
    <source data-srcset="island-s.webp 512w" media="(max-width: 512px)" sizes="100vw" type="image/webp"/>

    <source data-srcset="island-original.avif" type="image/avif"/>
    <source data-srcset="island-orig.webp" type="image/webp"/>

    <source data-srcset="island-xl.jpg 1200w" media="(min-width: 1025px)" sizes="(min-width: 1200px) 1200px, 100vw"/>
    <source data-srcset="island-l.jpg 1024w" media="(min-width: 673px) and (max-width: 1024px)" sizes="(min-width: 1200px) 1200px, 100vw"/>
    <source data-srcset="island-m.jpg 672w" media="(min-width: 513px) and (max-width: 672px)" sizes="(min-width: 1200px) 1200px, 100vw"/>
    <source data-srcset="island-s.jpg 512w" media="(max-width: 512px)" sizes="100vw"/>

    <img src="data:image/svg+xml;base64,PD94bWwg..." data-srcset="island-original.jpg" loading="lazy" width="1200" height="800" alt="Gruppenfoto vor dem Sommerhaus: Joschi, Nina, Annika und Jeff mit Kapuzen und Caps"/>
    <noscript>
        <img src="island-original.jpg" width="1200" height="800" alt="Gruppenfoto vor dem Sommerhaus: Joschi, Nina, Annika und Jeff mit Kapuzen und Caps"/>
    </noscript>
</picture>

Ganz offensichtlich wäre es nicht zumutbar, die Pflege eines solchen Monsters manuell betreiben und redaktionell bewältigen zu wollen. Deshalb haben wir unserem TYPO3 beigebracht, <picture>-Elemente wie das dargestellte vollautomatisch und mit vertretbarem, redaktionellen Zutun zu generieren. Soviel vorweg: Unsere dafür zuständige TYPO3-Extension tw_base ist bislang nur sehr mäßig dokumentiert, aber immerhin Open Source und für alle Interessierten herunterzuladen. Welche Werkzeuge die Extension zur Erzeugung responsiver Bilder mit sich bringt, beschreiben die folgenden Abschnitte.

Fluid-ViewHelper

Die eigentliche Hauptarbeit bei der Konstruktion des <picture>-Elements erledigt der Fluid-ViewHelper base:media, der Bestandteil unserer tw_base-Extension ist und die Verarbeitung intern an ein ResponsiveImagesUtility weiterdeligiert. Eine ausführliche Dokumentation der Argumente und Merkmale des ViewHelpers würde den Rahmen dieses Artikels sprengen, aber zumindest wollen wir ihn einmal in Aktion zeigen:

<base:media
    id="{imageId}"
    class="Image__resource"
    file="{file}"
    width="{dimensions.width}"
    height="{dimensions.height}"
    noTitle="true"
    alt="{alt}"
    srcset="{settings.media.srcset}"
    sizes="{settings.media.sizes}"
    picturefill="{settings.media.picturefill}"
    breakpoints="{breakpoints}"
    responsive="{responsive}"
    lazyload="{lazyload}"
    inline="{inline}"
    skipConverter="{skipConverter}"
/>

Die Argumente steuern unter anderem, welches Originalbild für die Erzeugung herangezogen wird und welche allgemeinen HTML-Attribute auf dem Bild platziert werden sollen. Es werden im System konfigurierte Breakpoints und die dazugehörigen srcset- und sizes -Parameter übergeben. Es wird bestimmt, ob Lazy Loading genutzt oder bestimmte Dateiformate ausgespart werden sollen. Es müssen nicht alle Argumente angegeben werden — auch einfachere Anwendungsfälle sind denkbar. Das Ergebnis des ViewHelper-Aufrufs ist die Ausgabe eines <picture>-Elements. Während der Aufbereitung werden die beteiligten Bildressourcen berechnet und im Dateisystem des Servers gespeichert.

Welche Bildformate am resultierenden <picture>-Element beteiligt sind, hängt maßgeblich davon ab, welche der folgenden Konverter und Kompressoren auf dem Server zur Verfügung stehen. Der ViewHelper nutzt deren Kommandozeilen-Schnittstellen, um die jeweiligen Formate zu erzeugen. Keine dieser Anwendungen zählt zur Standardinstallation eines einfachen Webservers — sie müssen vorab auf Betriebssystemebene installiert werden (je nach Serverumgebung stehen hierfür womöglich fertige Pakete zur Installation bereit).

MozJPEG-Kompressor

Der MozJPEG-Kompressor ist wohl der fortschrittlichste Encoder für JPEG-Dateien und kann diese in der Regel durch Entfernen unnötiger Datei-Bestandteile und weiterer Optimierungen merklich verkleinern. Das Mozilla JPEG Encoder Project steht frei auf Github zum Download zur Verfügung und ist speziell für die Aufbereitung von JPEGs für den Einsatz im Web gedacht.

Ein Aufruf des Encoders durch den base:media-ViewHelper sieht auf der Kommandozeile etwa so aus:

jpegtran -progressive -copy none source.jpg > target.jpg

Der ViewHelper optimiert die behandelten JPEGs in place — er tauscht also die Originalbilder gegen die optimierten Versionen aus. Mehrfaches Optimieren hat beim MozJPEG-Encoder keinen negativen Effekt, da er weder Qualität noch Kompatibilität der Bilder beeinflusst, sondern lediglich ihre interne Struktur optimiert.

WebP-Konverter

Der WebP-Encoder nimmt verschiedenste Bildformate entgegen und erzeugt daraus WebP-Dateien, die bei vergleichbarer Qualität die Dateigröße von JPEG-Dateien deutlich unterschreiten. Format und Encoder wurden von Google entwickelt und stehen dort zum Download bereit. Der Encoder unterstützt sowohl verlustbehaftete, als auch verlustfreie Konvertierung, wobei für unseren Einsatz nur die verlustbehaftete Konvertierung in Frage kommt — anders wäre eine Verringerung der Dateigröße unserer Bilder nicht zu erreichen.

Der Encoder kennt eine Vielzahl möglicher Argumente, aber der Aufruf durch unseren ViewHelper gestaltet sich recht einfach:

cwebp -q <quality> source.jpg -o target.webp

Der quality-Parameter liegt zwischen 0 und 100 und entscheidet über den Kompressionsgrad (100 bedeutet verlustfrei). Wir können den Kompressionsgrad von TYPO3 aus einstellen und erzielen mit 80 derzeit zufriedenstellende Ergebnisse.

AVIF-Konverter

Der brandneue AVIF-Encoder ist ebenfalls frei verfügbar und erzielt in der Dateigröße nochmals kleinere Dateien als WebP. Das Format leitet sich aus dem AV1-Videoformat ab und wird derzeit nur vom Chrome-Browser nativ unterstützt (Stand September 2020). Die Kodierung von AVIF-Dateien ist vergleichsweise zeit- und ressourcenintensiv, dafür lassen sich die Ergebnisse wirklich sehen.

Beim Aufruf des AVIF-Encoders müssen ein paar mehr Argumente übergeben werden — auch diese können wir von TYPO3 aus einstellen:

avifenc --min <min> --max <max> -j <workers> source.jpg target.avif

minundmax` beschreiben ein Qualitätsfenster, innerhalb dessen sich der Kompressionsgrad bewegen soll. Beide Werte liegen zwischen 0 (verlustfrei) und 63 — wir arbeiten aktuell mit 35 und 50. Insbesondere die Minimalkompression entscheidet über die Dateigröße und Qualität der resultierenden AVIF-Dateien.

Mit dem workers-Argument lässt sich die Anzahl von Teilprozessen festlegen, die der Encoder intern nutzt. Durch Erhöhung dieses Werts kann der Kodierungsvorgang auf mehrere Rechnerkerne verteilt und die benötigte Zeit damit spürbar verkürzt werden. Der Wert ist also hardwareabhängig zu wählen. In jedem Fall sollte man je nach Bilddimensionen mit mehreren Sekunden bis Minuten für die Konvertierung eines qualitativ hochwertigen Bilds rechnen.

Wer sich tiefergehend mit dem neuen AVIF-Format beschäftigen möchte, dem sei der ebenfalls soeben erschienene, englischsprachige Artikel unseres Freundes Matthias Ott empfohlen, oder die äußerst umfassenden Analysen von Jake Archibald.

SQIP-Generator & SVG-Kompressor

An der Erzeugung unserer SQIP-Vorschaubilder sind zwei unabhängige Anwendungen beteiligt: Zunächst errechnet das auf der Programmiersprache Go basierende primitive eine vektorbasierte SVG-Version des Originalbildes. Der Abstraktionsgrad kann dabei über mode und die Anzahl der zu erzeugenden Polygone num gesteuert werden (beide Werte sind wiederum über das TYPO3-Backend einstellbar):

primitive -i source.jpg -o target.svg -m <mode> -n <num>

Anschließend nutzen wir das Node-basierte SVGO, um die errechnete SVG-Vorschau in ihrer Dateigröße und Komplexität zu optimieren:

svgo --multipass -q -i target.svg

In einem letzten Arbeitsgang integriert unser PrimitiveLqipService schließlich einen <feGaussianBlur>-Filter in die SVG-Datei, so dass die abstrakten Polygone stark weichgezeichnet dargestellt werden. Die resultierende SVG-Datei wird in unseren HTML-Quelltext integriert und übernimmt die Aufgabe der Lazy-Loading-Vorschau.

Zusammenfassung

Die Auseinandersetzung mit Bildern für die Verwendung im Web ist nach wie vor nicht trivial — aber umso lohnenswerter, wenn man die vielfältigen Vorzüge kennt, die einem das <picture>-Element in Kombination mit modernen Bildformaten frei Haus liefert. In unserem Beitrag haben wir gezeigt, wie wir bedarfsgerechte Bilder mit optimierten Dimensionen, Motiven und Formaten zur Verfügung stellen und erst bei Bedarf zum Browser übertragen. Auf unserer TYPO3-basierten Website nutzen wir für die automatisierte Umsetzung die selbstentwickelte TYPO3-Extension tw_base, die frei zum Download zur Verfügung steht.