Ressourcenauswahl mit Clienthinweisen automatisieren

Ilja Grigorik

Die Entwicklung für das Web bietet Ihnen eine unvergleichliche Reichweite. Ihre Webanwendung ist nur einen Klick entfernt und ist auf fast allen verbundenen Geräten wie Smartphones, Tablets, Laptops und Computern, Fernsehern und vielem mehr verfügbar, unabhängig von der Marke oder der Plattform. Für eine optimale Nutzung haben Sie eine responsive Website erstellt, die die Darstellung und die Funktionalität an jeden Formfaktor anpasst. 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 Rendering-Pfad optimiert, Ihre Textressourcen komprimiert und im Cache gespeichert. Nun prüfen Sie den Großteil der übertragenen Byte. Das Problem ist, dass die Bildoptimierung schwer ist:

  • Das richtige Format bestimmen (Vektor oder Raster)
  • Die optimalen Codierungsformate (JPEG, WEBP usw.) bestimmen
  • Die richtigen Komprimierungseinstellungen bestimmen (verlustbehaftet oder verlustfrei)
  • Festlegen, welche Metadaten beibehalten oder entfernt werden sollen
  • Mehrere Varianten für jedes Display und DPR-Auflösung erstellen
  • ...
  • Netzwerktyp, Geschwindigkeit und Einstellungen des Nutzers berücksichtigen

Einzeln sind dies bekannte Probleme. Zusammen schaffen sie einen großen Optimierungsbereich, den wir (die Entwickler) oft übersehen oder vernachlässigen. Menschen machen es mühsam, denselben Suchbereich wiederholt zu erkunden, insbesondere wenn viele Schritte erforderlich sind. Computer hingegen sind für solche Aufgaben besonders gut geeignet.

Die Antwort auf eine gute und nachhaltige Optimierungsstrategie für Bilder und andere Ressourcen mit ähnlichen Eigenschaften ist einfach: Automatisierung. Wenn Sie Ihre Ressourcen von Hand anpassen, haben Sie es falsch gemacht: Sie werden vergessen, Sie werden faul oder jemand anderes macht diesen Fehler für Sie. Das ist garantiert.

Die Saga des leistungsbewussten Entwicklers

Die Suche über die Bildoptimierung umfasst zwei verschiedene Phasen: Build-Zeit und Laufzeit.

  • Einige Optimierungen sind in die Ressource selbst integriert, z.B. die Auswahl des geeigneten Formats und Codierungstyps, die Feinabstimmung der Komprimierungseinstellungen für jeden Encoder, das Entfernen unnötiger Metadaten usw. Diese Schritte können zur Erstellungszeit ausgeführt werden.
  • Andere Optimierungen werden durch den Typ und die Eigenschaften des Clients bestimmt, der sie anfordert, und müssen zur Laufzeit durchgeführt werden: Auswahl der geeigneten Ressource für den DPR des Clients und die gewünschte Anzeigebreite des Clients, Berücksichtigung der Netzwerkgeschwindigkeit des Clients, der Nutzer- und Anwendungseinstellungen usw.

Die Tools für die Erstellung sind vorhanden, könnten aber verbessert werden. Zum Beispiel lassen sich viele Einsparungen erzielen, wenn Sie die Qualitätseinstellung für jedes Bild und jedes Bildformat dynamisch anpassen. Es gibt jedoch noch keine Verwendung außerhalb der Forschung. Dieser Bereich ist bereit für Innovationen, aber in diesem Beitrag lasse ich es gleich. Konzentrieren wir uns auf die Laufzeit.

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

Das Anwendungs-Intent ist sehr einfach: das Bild wird bei 50% des Darstellungsbereichs des Nutzers abgerufen und angezeigt. Hier waschen sich fast alle Designschaffenden die Hände und Köpfe für die Bar. Der leistungsbewusste Entwickler im Team hat eine lange Nacht durch:

  1. Für eine optimale Komprimierung möchte sie für jeden Client das optimale Bildformat verwenden: WebP für Chrome, JPEG XR für Edge und JPEG für den Rest.
  2. Um die beste Bildqualität zu erzielen, muss sie mehrere Varianten jedes Bildes mit unterschiedlichen Auflösungen generieren: 1x, 1,5x, 2x, 2,5x, 3x und vielleicht auch etwas mehr dazwischen.
  3. Um die Auslieferung unnötiger Pixel zu vermeiden, muss sie verstehen, was „50% des Darstellungsbereichs des Nutzers eigentlich bedeutet“ – es gibt viele verschiedene Breiten des Darstellungsbereichs.
  4. Idealerweise möchte sie außerdem eine ausfallsichere Umgebung bieten, bei der Nutzer in langsameren Netzwerken automatisch eine niedrigere Auflösung abrufen. Schließlich geht es um die Zeit zum Glas.
  5. Die Anwendung stellt auch einige Nutzersteuerelemente bereit, die beeinflussen, welche Bildressource abgerufen werden soll, sodass auch diese berücksichtigt werden müssen.

Und dann erkennt die Designschaffenden, dass sie ein anderes Bild mit 100% Breite anzeigen muss, wenn der Darstellungsbereich klein ist, um die Lesbarkeit zu optimieren. Dies bedeutet, dass wir denselben Vorgang nun für ein weiteres Asset wiederholen und dann den Abruf von der Größe des Darstellungsbereichs abhängig machen müssen. Habe ich erwähnt, dass das Ganze kompliziert ist? Also, gut, legen wir los. Mit dem picture-Element kommen wir 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 zur Verfügung gestellt, um die Variabilität der DPR und der Breite des Darstellungsbereichs des Clientgeräts zu berücksichtigen. Hut ab!

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 lässt der Verarbeitungsalgorithmus des User-Agents in manchen Fällen festlegen, welche Ressource er abruft (siehe Schritt 5). Wir müssen nur hoffen, dass der User-Agent genug klug ist. Hinweis: Keine der aktuellen Implementierungen ist dies. Ebenso gibt es keine Hooks im Element picture, die eine anwendungsspezifische Logik ermöglichen, die App- oder Nutzereinstellungen berücksichtigt. Um die letzten beiden Bits zu erhalten, müssten wir die gesamte obige Logik in JavaScript verschieben. Dadurch gehen jedoch die Vorabladevorgänge für den Scanner verloren, die von picture angeboten werden. Hmm.

Abgesehen von diesen Einschränkungen funktioniert es. Zumindest für diesen speziellen Inhalt. Die eigentliche und langfristige Herausforderung hier besteht darin, dass wir nicht davon ausgehen können, dass die Designschaffenden oder Entwickler diesen Code für jedes einzelne Asset von Hand erstellen. Der erste Versuch ist ein lustiges Denkspiel, verliert aber sofort seinen Reiz. Wir brauchen Automatisierung. Vielleicht können uns die IDEs oder andere Tools zur Inhaltstransformation retten und den obigen Textbaustein automatisch generieren.

Ressourcenauswahl mit Clienthinweisen automatisieren

Atmen Sie tief durch, setzen Sie Ihren Unglauben zurück und betrachten Sie nun 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>

Ob Sie es glauben oder nicht, das obige Beispiel ist ausreichend, um dieselben Funktionen wie das viel längere Bild-Markup oben bereitzustellen. Außerdem haben Entwickler die vollständige Kontrolle darüber, wie, welche und wann die Bildressourcen abgerufen werden. Die „Magie“ befindet sich in der ersten Zeile, die die Berichterstellung zu Clienthinweisen aktiviert und den Browser anweist, dem Server das Pixelverhältnis des Geräts (DPR), die Breite des Darstellungsbereichs für das Layout (Viewport-Width) und die beabsichtigte Breite des Darstellungsbereichs (Width) der Ressourcen zu übermitteln.

Wenn Clienthinweise aktiviert sind, werden im resultierenden clientseitigen Markup nur die Darstellungsanforderungen beibehalten. Der Designer muss sich nicht um Bildtypen, Clientauflösungen, optimale Haltepunkte zur Reduzierung der übergebenen Byte oder andere Kriterien für die Ressourcenauswahl kümmern. Seien wir ehrlich, sie haben es noch nie gemacht und das sollte nicht unbedingt der Fall sein. Außerdem muss der Entwickler das obige Markup nicht neu schreiben und erweitern, da die tatsächliche Ressourcenauswahl vom Client und vom Server ausgehandelt wird.

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

Verhandlungsdiagramm für Kundenhinweise

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

Die nächsten drei Anfrageheader sind die Client-Hinweis-Header, die das Pixelverhältnis des Geräts des Clients (3x), die Breite des Layout-Darstellungsbereichs (460 px) und die beabsichtigte Anzeigebreite der Ressource (230 px) angeben. Dadurch werden dem Server alle erforderlichen Informationen bereitgestellt, um anhand eigener Richtlinien die optimale Bildvariante auszuwählen: Verfügbarkeit vorgenerierter Ressourcen, Kosten für die Neucodierung oder Größenänderung einer Ressource, Beliebtheit einer Ressource, aktuelle Serverlast usw. In diesem speziellen Fall verwendet der Server die Hinweise DPR und Width und gibt eine WebP-Ressource zurück, wie durch die Header Content-Type, Content-DPR und Vary angegeben.

Hier gibt es keine Magie. Wir haben die Ressourcenauswahl aus dem HTML-Markup in die Anfrage-Antwort-Verhandlung zwischen Client und Server verschoben. Beim HTML-Code geht es daher nur um Präsentationsanforderungen. Wir können davon ausgehen, dass jeder Designer und Entwickler schreibt, während die Suche im Bereich der Bildoptimierung auf Computer übertragen wird und nun problemlos in großem Maßstab automatisiert werden kann. Erinnern Sie sich an unseren leistungsbewussten Entwickler? Sie hat nun die Aufgabe, einen Bilddienst zu schreiben, der die bereitgestellten Hinweise nutzt und die entsprechende Antwort zurückgibt: Sie kann eine beliebige Sprache oder einen beliebigen Server verwenden oder einen Drittanbieterdienst oder ein CDN dies für sie erledigen lassen.

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

Erinnerst du dich auch an den Typ oben? Mit Clienthinweisen ist das bescheidene Bild-Tag jetzt DPR-, Darstellungsbereichs- und Breitenbewusstsein, ohne zusätzliches Markup. Wenn du Artdirection hinzufügen musst, kannst du das picture-Tag verwenden, wie oben dargestellt. Ansonsten sind all deine vorhandenen Image-Tags viel intelligenter geworden. Kundenhinweise verbessern vorhandene img- und picture-Elemente.

Mit dem Service Worker die Ressourcenauswahl 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, neu zu schreiben, im Cache zu speichern und sogar zu synthetisieren. Bei Images ist das nicht anders. Wenn Clienthinweise aktiviert sind, kann der aktive ServiceWorker die Image-Anfragen identifizieren, die bereitgestellten Clienthinweise prüfen und eine 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])
    }
    ...
}
&quot;serviceWorker&quot; von Clienthinweisen.

Mit ServiceWorkern haben Sie clientseitige Kontrolle über die Ressourcenauswahl. Das ist entscheidend. Lassen Sie das im Hinterkopf, denn die Möglichkeiten sind nahezu unendlich:

  • Sie können die vom User-Agent festgelegten Headerwerte für Client-Hinweise neu schreiben.
  • Sie können der Anfrage neue Werte für Client-Hinweis-Header anhängen.
  • Sie können die URL neu schreiben und die Bildanfrage auf einen alternativen Server (z.B. CDN) verweisen.
    • Sie können sogar die Hinweiswerte 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 für die Bereitstellung von Ressourcen definieren.
  • Sie können Ihre Antwort an die Konnektivität der Nutzer anpassen.
  • Sie können das Überschreiben von Anwendungs- und Nutzereinstellungen berücksichtigen.
  • Du kannst alles tun, was dein Herz begehrt.

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

Häufig gestellte Fragen zu Clienthinweisen

  1. Wo sind Client-Hinweise verfügbar? Wird in Chrome 46 ausgeliefert. Wird in Firefox und Edge geprüft.

  2. Warum können Kundenhinweise aktiviert werden? Wir möchten den Aufwand für Websites, die keine Client-Hinweise verwenden, minimieren. Zum Aktivieren von Clienthinweisen sollte die Website den Accept-CH-Header oder eine entsprechende <meta http-equiv>-Anweisung im Seiten-Markup angeben. Wenn eine dieser Optionen vorhanden ist, hängt der User-Agent allen Anfragen von Unterressourcen die entsprechenden Hinweise an. In Zukunft werden wir möglicherweise einen zusätzlichen Mechanismus anbieten, um diese Einstellung für einen bestimmten Ursprung beizubehalten. Dadurch können dieselben Hinweise auch bei Navigationsanfragen gesendet werden.

  3. Warum benötigen wir Clienthinweise, wenn wir ServiceWorker verwenden? ServiceWorker hat keinen Zugriff auf Informationen zum Layout, zu Ressourcen und zur Breite des Darstellungsbereichs. Zumindest nicht ohne kostspielige Roundtrips und eine erhebliche Verzögerung der Bildanfrage – z.B. wenn eine Bildanfrage vom Preload-Parser initiiert wird. Clienthinweise werden in den Browser integriert, um diese Daten als Teil der Anfrage verfügbar zu machen.

  4. Sind Clienthinweise nur für Bildressourcen verfügbar? Der wichtigste Anwendungsfall für die DPR-, Darstellungsbereichsbreite und -Breite ist das Aktivieren der Ressourcenauswahl für Bild-Assets. Allerdings werden unabhängig vom Typ für alle Unterressourcen dieselben Hinweise bereitgestellt. CSS- und JavaScript-Anfragen erhalten beispielsweise dieselben Informationen und können auch zur Optimierung dieser Ressourcen verwendet werden.

  5. Warum wird bei einigen Bildanfragen die Breite nicht gemeldet? Der Browser kennt die gewünschte Anzeigebreite möglicherweise nicht, weil die Website von der intrinsischen Größe des Bildes abhängt. Daher wird der Hinweis für die Breite bei solchen Anfragen und bei Anfragen ohne „Anzeigebreite“ weggelassen, z.B. bei einer JavaScript-Ressource. Wenn Sie Hinweise für die Breite erhalten möchten, müssen Sie einen Größenwert für Ihre Bilder 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). Beispielsweise können Sie ganz einfach NetInfo-basierte Informationen hinzufügen, um den aktuellen Verbindungstyp anzugeben – siehe Funktionsberichten mit ServiceWorker. Die in Chrome enthaltenen „nativen“ Hinweise (DPR, Width, Resource-Width) werden im Browser implementiert, da eine reine Software-basierte Implementierung alle Bildanfragen verzögern würde.

  7. Wo finde ich weitere Informationen und Demos? Sehen Sie sich das Erläuterungsdokument an. Wenn Sie Feedback oder andere Fragen haben, können Sie gern ein Problem auf GitHub eröffnen.