Ressourcenauswahl mit Clienthinweisen automatisieren

Ilya Grigorik
Ilya Grigorik

Wenn Sie für das Web entwickeln, haben Sie eine unübertroffene Reichweite. Ihre Webanwendung ist nur einen Klick entfernt und auf fast allen verbundenen Geräten verfügbar – Smartphone, Tablet, Laptop und Computer, Fernseher und mehr – unabhängig von der Marke oder Plattform. Sie haben eine responsive Website entwickelt, die Darstellung und Funktionen an jeden Formfaktor anpasst, um die Nutzerfreundlichkeit zu optimieren. Jetzt gehen Sie Ihre Leistungscheckliste durch, um dafür zu sorgen, dass die Anwendung so schnell wie möglich geladen wird: Sie haben Ihren kritischen Renderingpfad optimiert, Ihre Textressourcen komprimiert und im Cache gespeichert und sehen sich jetzt Ihre Bildressourcen an, die oft den Großteil der übertragenen Bytes ausmachen. Das Problem ist, dass die Bildoptimierung schwierig ist:

  • Das richtige Format auswählen (Vektor- oder Rasterformat)
  • Optimale Codierungsformate (z. B. JPEG, WEBP) ermitteln
  • Die richtigen Komprimierungseinstellungen festlegen (verlustbehaftet oder verlustfrei)
  • Festlegen, welche Metadaten beibehalten oder entfernt werden sollen
  • Für jedes Display und jede DPR-Auflösung mehrere Varianten erstellen
  • Netzwerktyp, Geschwindigkeit und Einstellungen des Nutzers berücksichtigen

Einzeln betrachtet sind dies gut bekannte Probleme. Zusammengenommen bieten sie ein großes Optimierungspotenzial, das wir (die Entwickler) oft übersehen oder vernachlässigen. Menschen sind nicht gut darin, denselben Suchraum wiederholt zu durchsuchen, insbesondere wenn viele Schritte erforderlich sind. Computer hingegen eignen sich hervorragend für diese Art von Aufgaben.

Die Antwort auf eine gute und nachhaltige Optimierungsstrategie für Bilder und andere Ressourcen mit ähnlichen Eigenschaften ist einfach: Automatisierung. Wenn Sie Ihre Ressourcen manuell optimieren, machen Sie es falsch: Sie werden es vergessen, sich zu sehr darauf verlassen oder jemand anderes wird diesen Fehler für Sie machen – garantiert.

Die Geschichte des leistungsbewussten Entwicklers

Die Suche im Bereich der Bildoptimierung umfasst zwei Phasen: die Build- und die Laufzeit.

  • Einige Optimierungen sind für die Ressource selbst charakteristisch, z.B. die Auswahl des geeigneten Formats und Codierungstyps, die Anpassung der Komprimierungseinstellungen für jeden Encoder oder das Entfernen unnötiger Metadaten. Diese Schritte können zur „Buildzeit“ ausgeführt werden.
  • Andere Optimierungen werden vom Typ und den Eigenschaften des anfragenden Clients bestimmt und müssen zur Laufzeit ausgeführt werden. Dazu gehört die Auswahl der geeigneten Ressource für die DPR und die beabsichtigte Darstellungsbreite des Clients unter Berücksichtigung der Netzwerkgeschwindigkeit, der Nutzer- und Anwendungseinstellungen usw.

Die Tools für die Buildzeit sind vorhanden, könnten aber verbessert werden. So lassen sich beispielsweise durch die dynamische Anpassung der Einstellung „Qualität“ für jedes Bild und jedes Bildformat große Einsparungen erzielen. Ich habe aber noch nie gesehen, dass jemand diese Funktion außerhalb von Forschungsprojekten verwendet. Dies ist ein Bereich, in dem es viel Raum für Innovationen gibt, aber für diesen Beitrag möchte ich es dabei belassen. Konzentrieren wir uns auf den Laufzeitteil der Story.

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

Die Anwendungsabsicht ist sehr einfach: Das Bild wird abgerufen und zu 50% des Darstellungsbereichs des Nutzers angezeigt. Hier waschen sich die meisten Designer die Hände und machen sich für die Bar bereit. In der Zwischenzeit steht dem leistungsbewussten Entwickler im Team eine lange Nacht bevor:

  1. Um die beste Komprimierung zu erzielen, möchte sie für jeden Client das optimale Bildformat verwenden: WebP für Chrome, JPEG XR für Edge und JPEG für alle anderen.
  2. Um die beste visuelle Qualität zu erzielen, muss sie mehrere Varianten jedes Bildes mit unterschiedlichen Auflösungen generieren: 1x, 1,5x, 2x, 2,5x, 3x und vielleicht sogar noch ein paar dazwischen.
  3. Um unnötige Pixel zu vermeiden, muss sie verstehen, was „50% des Darstellungsbereichs des Nutzers“ tatsächlich bedeutet. Es gibt viele verschiedene Darstellungsbereichsbreiten.
  4. Idealerweise möchte sie auch eine robuste Lösung bereitstellen, bei der Nutzer mit langsameren Netzwerken automatisch eine niedrigere Auflösung abrufen. Schließlich geht es um die Zeit für ein Glas.
  5. Die Anwendung bietet auch einige Nutzersteuerelemente, die sich darauf auswirken, welche Bildressource abgerufen werden soll. Dies muss ebenfalls berücksichtigt werden.

Außerdem stellt die Designerin fest, dass sie ein anderes Bild mit 100% Breite anzeigen muss, wenn die Größe des Darstellungsbereichs klein ist, um die Lesbarkeit zu optimieren. Das bedeutet, dass wir denselben Vorgang für ein weiteres Asset wiederholen und dann das Abrufen von der Größe des Darstellungsbereichs abhängig machen müssen. Habe ich schon erwähnt, dass das schwierig ist? Ok, dann legen wir los. Mit dem Element picture kommen wir schon ziemlich weit:

<picture>
    <!-- serve WebP to Chrome and Opera -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.webp 200w, /image/thing-400.webp 400w,
        /image/thing-800.webp 800w, /image/thing-1200.webp 1200w,
        /image/thing-1600.webp 1600w, /image/thing-2000.webp 2000w"
    type="image/webp">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.webp 200w, /image/thing-crop-400.webp 400w,
        /image/thing-crop-800.webp 800w, /image/thing-crop-1200.webp 1200w,
        /image/thing-crop-1600.webp 1600w, /image/thing-crop-2000.webp 2000w"
    type="image/webp">
    <!-- serve JPEGXR to Edge -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpgxr 200w, /image/thing-400.jpgxr 400w,
        /image/thing-800.jpgxr 800w, /image/thing-1200.jpgxr 1200w,
        /image/thing-1600.jpgxr 1600w, /image/thing-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpgxr 200w, /image/thing-crop-400.jpgxr 400w,
        /image/thing-crop-800.jpgxr 800w, /image/thing-crop-1200.jpgxr 1200w,
        /image/thing-crop-1600.jpgxr 1600w, /image/thing-crop-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <!-- serve JPEG to others -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpg 200w, /image/thing-400.jpg 400w,
        /image/thing-800.jpg 800w, /image/thing-1200.jpg 1200w,
        /image/thing-1600.jpg 1600w, /image/thing-2000.jpg 2000w">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpg 200w, /image/thing-crop-400.jpg 400w,
        /image/thing-crop-800.jpg 800w, /image/thing-crop-1200.jpg 1200w,
        /image/thing-crop-1600.jpg 1600w, /image/thing-crop-2000.jpg 2000w">
    <!-- fallback for browsers that don't support picture -->
    <img src="/image/thing.jpg" width="50%">
</picture>

Wir haben die Art Direction und die Formatauswahl übernommen und sechs Varianten jedes Bildes erstellt, um die Variabilität der DPR und der Ansichtsbreite des Geräts des Kunden zu berücksichtigen. Beeindruckend!

Leider können wir mit dem Element picture keine Regeln dafür definieren, wie es sich je nach Verbindungstyp oder -geschwindigkeit des Clients verhalten soll. Allerdings ermöglicht der Verarbeitungsalgorithmus in einigen Fällen dem User-Agent, die abgerufene Ressource anzupassen (siehe Schritt 5). Wir müssen nur hoffen, dass der User-Agent intelligent genug ist. Hinweis: Keine der aktuellen Implementierungen ist dies. Ebenso gibt es im picture-Element keine Hooks, die eine app-spezifische Logik zulassen, die App- oder Nutzereinstellungen berücksichtigt. Um diese letzten beiden Bits zu erhalten, müssten wir die gesamte oben stehende Logik in JavaScript verschieben. Dadurch gehen jedoch die von picture angebotenen Optimierungen für den Preloader verloren. Hmm.

Abgesehen von diesen Einschränkungen funktioniert es. Zumindest für dieses bestimmte Asset. Die eigentliche und langfristige Herausforderung besteht darin, dass wir nicht erwarten können, dass Designer oder Entwickler solchen Code für jedes einzelne Asset manuell erstellen. Beim ersten Versuch ist es ein unterhaltsames Denkspiel, aber danach verliert es sofort seinen Reiz. Wir brauchen Automatisierung. Vielleicht können wir die Vorlage oben automatisch mithilfe der IDE oder anderer Tools zur Inhaltstransformation generieren.

Ressourcenauswahl mit Clienthinweisen automatisieren

Atmen Sie tief durch, lassen Sie sich auf das Folgende ein und betrachten Sie das folgende Beispiel:

<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">
...
<picture>
    <source media="(min-width: 50em)" sizes="50vw" srcset="/image/thing">
    <img sizes="100vw" src="/image/thing-crop">
</picture>

Das obige Beispiel reicht aus, um dieselben Funktionen wie das viel längere Bild-Markup oben zu bieten. Außerdem haben Entwickler damit die volle Kontrolle darüber, wie, welche und wann die Bildressourcen abgerufen werden. Das „Zauberwort“ befindet sich in der ersten Zeile, die die Berichterstellung für Client-Hinweise aktiviert und dem Browser mitteilt, dem Server das Pixelverhältnis des Geräts (DPR), die Breite des Layout-Viewports (Viewport-Width) und die beabsichtigte Darstellungsbreite (Width) der Ressourcen mitzuteilen.

Wenn Clienthinweise aktiviert sind, enthält das resultierende clientseitige Markup nur die Darstellungsanforderungen. Der Designer muss sich nicht um Bildtypen, Clientauflösungen, optimale Unterbrechungen zur Reduzierung der gesendeten Bytes oder andere Kriterien für die Ressourcenauswahl kümmern. Aber das war nie der Fall und das sollte es auch nicht sein. Außerdem muss der Entwickler das obige Markup nicht umschreiben und erweitern, da die tatsächliche Ressourcenauswahl zwischen Client und Server ausgehandelt wird.

Chrome 46 bietet native Unterstützung für die DPR-, Width- und Viewport-Width-Hinweise. Die Hinweise sind standardmäßig deaktiviert. <meta http-equiv="Accept-CH" content="..."> dient als Aktivierungssignal, das Chrome anweist, die angegebenen Header an ausgehende Anfragen anzuhängen. Sehen wir uns nun die Anfrage- und Antwortheader einer Beispielanfrage für Bilder an:

Diagramm zur Verhandlung von Client-Hints

Chrome gibt seine Unterstützung für das WebP-Format über den Accept-Anfrageheader an. Der neue Edge-Browser gibt seine Unterstützung für JPEG XR ebenfalls über den Accept-Header an.

Die nächsten drei Anfrageheader sind die Client-Hinweisheader, die das Pixelverhältnis des Geräts des Clients (3x), die Breite des Layout-Darstellungsbereichs (460 px) und die beabsichtigte Darstellungsbreite der Ressource (230 px) angeben. So erhält der Server alle erforderlichen Informationen, um anhand seiner eigenen Richtlinien die optimale Bildvariante auszuwählen: Verfügbarkeit vorab generierter Ressourcen, Kosten für die erneute Codierung oder Größenänderung einer Ressource, Beliebtheit einer Ressource, aktuelle Serverauslastung usw. In diesem Fall verwendet der Server die Hinweise DPR und Width und gibt eine WebP-Ressource zurück, wie aus den Headern Content-Type, Content-DPR und Vary hervorgeht.

Es gibt hier keine Magie. Wir haben die Ressourcenauswahl aus dem HTML-Markup in die Anfrage-/Antwort-Verhandlung zwischen Client und Server verschoben. Daher befasst sich das HTML nur mit den Anforderungen an die Präsentation und kann von jedem Designer und Entwickler geschrieben werden. Die Suche im Bereich der Bildoptimierung wird dagegen an Computer übergeben und kann jetzt problemlos im großen Maßstab automatisiert werden. Erinnern Sie sich an unseren leistungsbewussten Entwickler? Ihre Aufgabe besteht nun darin, einen Bilddienst zu schreiben, der die bereitgestellten Hinweise nutzen und die entsprechende Antwort zurückgeben kann. Sie kann eine beliebige Sprache oder einen beliebigen Server verwenden oder einen Drittanbieterdienst oder ein CDN in ihrem Namen verwenden.

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

Erinnern Sie sich an den Mann oben? Mit Client-Hinweisen ist das einfache Bild-Tag jetzt ohne zusätzliches Markup auf DPR, Viewport und Breite bezogen. Wenn Sie Art-Direction hinzufügen möchten, können Sie das picture-Tag verwenden, wie oben gezeigt. Ansonsten sind alle Ihre vorhandenen Bild-Tags jetzt viel intelligenter. Mithilfe von Clienthinweisen lassen sich vorhandene img- und picture-Elemente optimieren.

Ressourcenauswahl mit Service Worker steuern

ServiceWorker ist im Grunde ein clientseitiger Proxy, der in Ihrem Browser ausgeführt wird. Er fängt alle ausgehenden Anfragen ab und ermöglicht es Ihnen, Antworten zu prüfen, umzuschreiben, im Cache zu speichern und sogar zu synthetisieren. Bei Bildern ist das nicht anders. Wenn Clienthinweise aktiviert sind, kann der aktive ServiceWorker die Bildanfragen identifizieren, die bereitgestellten Clienthinweise prüfen und seine eigene Verarbeitungslogik definieren.

self.onfetch = function(event) {
    var req = event.request.clone();
    console.log("SW received request for: " + req.url)
    for (var entry of req.headers.entries()) {
    console.log("\t" + entry[0] +": " + entry[1])
    }
    ...
}
Client-Hinweise für serviceWorker.

Mit ServiceWorker haben Sie die volle clientseitige Kontrolle über die Ressourcenauswahl. Das ist wichtig. Die Möglichkeiten sind nahezu unbegrenzt:

  • Sie können die vom User-Agent festgelegten Werte für Client-Hints-Header neu schreiben.
  • Sie können der Anfrage neue Werte für Clienthinweis-Header anhängen.
  • Sie können die URL umschreiben und die Bildanfrage an einen alternativen Server (z.B. CDN) weiterleiten.
    • Sie können die Hinweiswerte auch aus den Headern in die URL selbst verschieben, wenn dies die Bereitstellung in Ihrer Infrastruktur vereinfacht.
  • Sie können Antworten im Cache speichern und eine eigene Logik dafür definieren, welche Ressourcen bereitgestellt werden.
  • Sie können Ihre Antwort an die Internetverbindung der Nutzer anpassen.
  • Sie können Überschreibungen von Anwendungs- und Nutzereinstellungen berücksichtigen.
  • Sie können wirklich alles tun, was Sie sich wünschen.

Das picture-Element bietet die erforderliche Art-Direction-Steuerung im HTML-Markup. Clienthinweise enthalten Anmerkungen zu den resultierenden Bildanfragen, die die Automatisierung der Ressourcenauswahl ermöglichen. ServiceWorker bietet Funktionen zur Verwaltung von Anfragen und Antworten auf dem Client. Das ist das erweiterbare Web in Aktion.

Häufig gestellte Fragen zu Client-Hints

  1. Wo sind Clienthinweise verfügbar? In Chrome 46 eingeführt. Wird in Firefox und Edge geprüft.

  2. Warum müssen die Clienthinweise aktiviert werden? Wir möchten den Overhead für Websites minimieren, auf denen keine Clienthinweise verwendet werden. Wenn Sie Clienthinweise aktivieren möchten, muss die Website die Accept-CH-Überschrift oder eine entsprechende <meta http-equiv>-Anweisung im Seiten-Markup enthalten. In diesem Fall fügt der User-Agent allen Anfragen für untergeordnete Ressourcen die entsprechenden Hinweise hinzu. In Zukunft werden wir möglicherweise einen zusätzlichen Mechanismus zur Speicherung dieser Einstellung für einen bestimmten Ursprung bereitstellen, damit bei Navigationsanfragen dieselben Hinweise gesendet werden können.

  3. Warum benötigen wir Clienthinweise, wenn wir ServiceWorker haben? Der ServiceWorker hat keinen Zugriff auf Informationen zu Layout, Ressourcen und Viewport-Breite. Zumindest nicht ohne kostspielige zusätzliche Abläufe und eine erhebliche Verzögerung der Bildanfrage, z.B. wenn eine Bildanfrage vom Preloader-Parser initiiert wird. Clienthinweise werden in den Browser eingebunden, um diese Daten als Teil der Anfrage verfügbar zu machen.

  4. Gelten Clienthinweise nur für Bildressourcen? Der Hauptanwendungszweck von Hinweisen zu DPR, Viewport-Width und Width besteht darin, die Ressourcenauswahl für Bild-Assets zu ermöglichen. Dieselben Hinweise werden jedoch für alle untergeordneten Ressourcen unabhängig vom Typ gesendet. So erhalten z. B. auch CSS- und JavaScript-Anfragen dieselben Informationen und können zur Optimierung dieser Ressourcen verwendet werden.

  5. Warum wird für einige Bildanfragen keine Breite angegeben? Der Browser kennt möglicherweise nicht die beabsichtigte Anzeigebreite, da die Website die Eigengröße des Bildes verwendet. Daher wird der Breiteshinweis für solche Anfragen und für Anfragen ohne „Bildschirmbreite“ (z.B. eine JavaScript-Ressource) weggelassen. Wenn Sie Hinweise zur Breite erhalten möchten, müssen Sie für Ihre Bilder einen Wert für „Größen“ angeben.

  6. Was ist mit <insert my favorite hint>? Mit ServiceWorker können Entwickler alle ausgehenden Anfragen abfangen und ändern (z.B. neue Header hinzufügen). So können Sie beispielsweise ganz einfach NetInfo-basierte Informationen hinzufügen, um den aktuellen Verbindungstyp anzugeben. Weitere Informationen finden Sie unter Funktionsberichte mit ServiceWorker. Die in Chrome bereitgestellten „nativen“ Hinweise (DPR, Width, Resource-Width) werden im Browser implementiert, da eine rein softwarebasierte Implementierung alle Bildanfragen verzögern würde.

  7. Wo erhalte ich weitere Informationen und wo kann ich weitere Demos ansehen? Sehen Sie sich das Erläuterungsdokument an und erstellen Sie ein Problem auf GitHub, wenn Sie Feedback oder andere Fragen haben.