- Da GitHub Pages Brotli nicht unterstützt, handelt es sich um ein Experiment, bei dem HTML als verlustfreies WebP-Bild kodiert und mit dem Bilddecoder des Browsers sowie JavaScript wiederhergestellt wird, um die übertragene Datenmenge zu reduzieren.
- Ein WASM-Brotli-Decoder verursacht
71 KiB~200 KBzusätzlichen Aufwand, und die Compression Streams API unterstützt nurgzip,deflateunddeflate-raw, sodass sie sich kaum als Umweg für Brotli-Decoding eignet. - Verlustfreies VP8L-WebP kann dank Prädiktor-Transformationen, Wiederverwendung von Huffman-Bäumen pro 16x16-Block und Color Cache auch bei textartigen Daten kleinere Ergebnisse als gzip erzielen.
- Das Test-HTML mit
439,478Bytes schrumpfte auf94,683Bytes mit gzip und auf43,182Bytes mit WebP; damit war es zwar größer als Brotli mit37 KiB, aber etwa 2,2-mal kleiner als gzip. - Wegen Anti-Fingerprinting-Rauschen in Canvas 2D, Änderungen an WebGL
readPixels, anfänglich leerem Bildschirm und Problemen bei der Scroll-Wiederherstellung ist es eher ein Hack, der Browser-Beschränkungen offenlegt, als etwas für den praktischen Einsatz.
Das Problem, dass Brotli auf GitHub Pages nicht nutzbar ist
- Beim Verkürzen der Ladezeit einer Seite bringt HTTP-Kompression mehr als HTML-Minifizierung.
- HTTP unterstützt über den Header
Content-Encodinggzip und Brotli.- gzip ist günstig und daher häufig standardmäßig aktiviert.
- Brotli komprimiert meist besser als gzip, ist aber deutlich langsamer.
- GitHub Pages unterstützt Brotli nicht. Dadurch wird der längste Artikel der Website,
Recovering garbled Bitcoin addresses, statt möglicher37 KiBmit Brotli zu92 KiBmit gzip. - Durch diesen Unterschied verlängert sich die Ladezeit unnötig um das 2,5-Fache.
Erste naheliegende Alternativen und wo sie scheitern
- Wenn GitHub das Hochladen und Ausliefern vorab komprimierter Brotli-Dateien unterstützen würde, ließe sich das Problem umgehen; eine solche Funktion gibt es aber nicht.
- Eine clientseitige Dekomprimierung direkt per JavaScript verliert wegen der Größe des WASM-Decoders ihren Vorteil.
- brotli-dec-wasm ist etwa
200 KBgroß. - tiny-brotli-dec-wasm ist
71 KiBgroß. - Der Vergleich wird dann gzip mit
92 KiBgegen Brotli-Inhalt mit37 KiB + 71 KiB, wodurch der Vorteil verschwindet.
- brotli-dec-wasm ist etwa
- Im HTTP-Stack des Browsers gibt es zwar einen Brotli-Decoder, aber DecompressionStream der Compression Streams API akzeptiert nur
gzip,deflateunddeflate-raw. - Selbst wenn
gzipmit Zopfli vorab komprimiert wird, bleiben86 KiB, also weiterhin mehr als bei Brotli.
Bildformate als Kompressionscontainer verwenden
- Da Browser Bilder dekodieren können, kann man Daten in Bildpixel schreiben und sie per Canvas API wieder auslesen, ohne eigene Dekompressionslogik einzubauen.
- GIF entfaltet Daten in Row-Major-Reihenfolge und wendet anschließend LZW an; da gzip mit DEFLATE als Ersatz für LZW entworfen wurde, ist hier kaum ein Vorteil zu erwarten.
- PNG nutzt DEFLATE, wendet aber zuerst eine Prädiktor-Transformation an, die nicht die ursprünglichen Pixel, sondern die Differenzen zu Nachbarpixeln komprimiert.
- Beispiel: Statt
[a, b, c, d]wird[a, b-a, c-b, d-c]komprimiert. - Je kleiner die Differenz zwischen Vorhersagewert und tatsächlichem Wert ist, desto günstiger ist das für die Huffman-Kompression.
- Beispiel: Statt
- Kern des Experiments ist, verlustfreies WebP, also VP8L, zur Kompression allgemeiner Byte-Daten zu nutzen.
Worin sich VP8L von gzip unterscheidet
- WebP hat verlustbehaftete und verlustfreie Varianten; hier geht es nur um das verlustfreie Format VP8L.
- VP8L nutzt wie PNG Prädiktor-Transformationen, verwendet aber statt DEFLATE ein von Google entwickeltes DEFLATE-ähnliches Verfahren.
- DEFLATE kann eine Datei in mehrere Teile aufteilen und für jeden Teil passende Huffman-Bäume verwenden.
- Wenn JavaScript, SVG und Markup in einem HTML gemischt sind, können unterschiedliche Bäume vorteilhaft sein.
- VP8L definiert eine beliebig große Huffman-Baum-Tabelle und kann für jeden 16x16-Pixel-Block einen anderen Baum verwenden.
- Wenn auf JavaScript CSS und dann wieder JavaScript folgt, muss DEFLATE ähnliche Bäume möglicherweise mehrfach kodieren.
- VP8L kann Bäume wiederverwenden und dadurch häufiger und günstiger zwischen ihnen wechseln.
- Der Color Cache von VP8L kann Werte kurz ausdrücken, indem er etwa sagt, dass ein kürzliches Pixel mit bestimmten Eigenschaften kopiert werden soll.
Erster WebP-Kompressionstest
- Die Testdatei ist das HTML zu
Recovering garbled Bitcoin addresses.- Originalgröße:
439,478Bytes gzip --best:94,683Bytes
- Originalgröße:
- Mit dem Rust-Crate webp wurden die Bytes in ein Graustufen-RGB-Bild umgewandelt und als verlustfreies WebP komprimiert.
- Graustufen wurden wegen der
subtract green-Transformation von WebP verwendet.- Bei Graustufen werden die R/B-Kanäle durch Subtraktion des G-Kanals praktisch zu 0.
- WebP kodiert die drei Kanäle mit separaten Huffman-Bäumen, sodass Kanäle mit konstantem Wert fast nur
O(1)Platz benötigen.
- Zunächst sollte ein
1xN-Bild erzeugt werden, aber WebP unterstützt maximal16383x16383, wodurch der FehlerVP8_ENC_ERROR_BAD_DIMENSIONauftrat. - In einer Form
16383xNergab sich45,604Bytes, also halb so viel wie gzip und sogar weniger als bzip2 mit49,764Bytes.
WebP-spezifische Anpassungen
- Wenn man bei einem breiten Bild Row-Major-Reihenfolge verwendet, landen in einem 16x16-Block Bytes, die in der Eingabe weit voneinander entfernt sind.
- Durch Umstellung auf ein hochkantiges, schmales Bild mit
27x16383schrumpfte das komprimierte Ergebnis auf43,232Bytes. - Der Kompressionsparameter method von
cwebpwurde von0~6verglichen.- method 0:
48,902 - method 1:
43,546 - method 2:
43,442 - method 3:
43,292 - method 4:
43,232 - method 5:
43,182 - method 6:
43,182
- method 0:
- method
5wurde gewählt, weil sie dieselbe Größe wie method6liefert, aber schneller ist. - In diesem Zustand ist WebP 2,2-mal kleiner als gzip und 1,2-mal größer als Brotli.
Benchmark mit mehreren Dateien
- Verglichen wurden snappy testdata, Canterbury Corpus und Large Corpus sowie zwei SVG-Dateien.
- Die Vergleichsformate waren
gzip --best,brotli --best,bzip2 --bestund das WebP-Kompressionsskript. - Abgesehen von sehr kleinen Dateien wie
grammar.lsp,xargs.1und einigen Ausnahmen war WebP fast immer besser als gzip. - Ausnahmen waren
kennedy.xlsundpaper-100k.pdf.paper-100k.pdfenthält nach19 KBXML komprimierte Daten, sodass faktisch kleine Daten gemessen werden.- Bei
kennedy.xlsist auch die relative Performance von Brotli/bzip2 ungewöhnlich; es könnte eine Datei mit vielen heterogenen Daten in enger Nachbarschaft sein, mit der Kompressoren schwer umgehen können.
- WebP ist tendenziell etwas schlechter als bzip2, liegt in manchen Fällen aber auch davor.
- WebP war außer bei Sonderfällen wie
fireworks.jpeg, die fast gleichförmigen zufälligen Blobs ähneln, immer schlechter als Brotli. - Bei großen Plain-Text-Daten lieferte es messbare Verbesserungen gegenüber gzip.
- Auch bei SVG-Dateien gab es Verbesserungen.
- Bei
html_x_4erreichte WebP eine Kompressionsrate von3.3%; das war schlechter als Brotli mit2.8%, aber deutlich besser als gzip mit13%.
Wiederherstellung mit JavaScript
- Das WebP-Decoding selbst lässt sich mit
fetch,createImageBitmap,OffscreenCanvas,getImageDataundTextDecoderumsetzen. - Dabei wird der R-Kanal der Pixel als ursprüngliche HTML-Bytes verwendet, als UTF-8 dekodiert und dann in
document.documentElement.innerHTMLeingefügt. - Da die Canvas API häufig für Fingerprinting genutzt wird, fügen manche Browser den Ergebnissen von
getImageDataRauschen hinzu.- Bei Firefox mit strict tracking protection können weniger als 1 % der Pixel betroffen sein.
- Im HTML erscheint dieses Rauschen wie Tippfehler.
- Mit WebGLs
readPixelsfunktionierte es damals ohne Rauschen.- WebGL unterstützt Texturen zuverlässig nur bis
2048x2048, daher müssen die Größenlimits erneut angepasst werden. - Der Wiederherstellungscode war nach Minifizierung etwa
550Bytes groß.
- WebGL unterstützt Texturen zuverlässig nur bis
- Zusammen mit WebP und Code ergaben sich
44 KiB, verglichen mit92 KiBbei gzip und37 KiBbei Brotli. - Seit dem 15. April 2026 fügt Firefox auch bei
readPixelsAnti-Fingerprinting hinzu, sodass diese Methode unverändert nicht mehr funktioniert.
Bildschirmflackern und Scroll-Probleme
- Da
awaitPromise-basiert verarbeitet wird, geht der Browser davon aus, dass die Skriptausführung beendet ist, bevor der WebP-Download abgeschlossen ist. - Weil das DOM noch leer ist, sieht der Nutzer für kurze Zeit einen leeren weißen Bildschirm.
- Als Abmilderung können Styles und die oberen etwa
8 KiBder Seite im gzip-HTML verbleiben, während nur der Inhalt unterhalb des Viewports als WebP komprimiert wird. - Auch die Wiederherstellung der Scroll-Position beim Neuladen wird zum Problem.
- Wenn man zum Beispiel bei
Y = 5000pxneu lädt, die Seitenhöhe aber0pxbeträgt, wird die Position zurückgesetzt. - Ein sehr großes temporäres
divkann dabei helfen.
- Wenn man zum Beispiel bei
- Statt
document.writemussdocument.documentElement.innerHTMLzugewiesen werden, damit das aktuelle Dokument aktualisiert wird, ohne es durch ein neues Dokument zu ersetzen.
WebP direkt in JavaScript einbetten
- Um die Latenz etwas weiter zu reduzieren, kann WebP direkt in JavaScript eingebettet werden.
- Der einfachste Ansatz ist eine base64 data URL.
- base64 vergrößert die ursprüngliche Größe um das
1,33-Fache, aber gzip gleicht diesen Zuwachs fast vollständig aus.- Wird
compressed.webpin base64 umgewandelt, entstehen57,576Bytes. - Mit gzip
--bestkomprimiert werden daraus43,519Bytes.
- Wird
- Ein komprimierter Blob wie WebP ist nahezu gleichmäßig zufällig; auf das Ergebnis der 8-Bit-zu-6-Bit-Transformation von base64 wirkt der Huffman-Baum von gzip praktisch wie eine Rücktransformation.
- Unicode und UTF-16 wären ebenfalls möglich, aber base64 bleibt als erste Lösung ausreichend.
Praktischer Einsatz und späterer Stand
- Zum Zeitpunkt der Erstellung des Artikels war diese Seite selbst ab dem Abschnitt „Fool me twice“ per WebP komprimiert, sofern es sich nicht um einen alten Browser oder eine Umgebung mit deaktiviertem JavaScript handelte.
- Das WebP-Bild der Seite ist im tatsächlichen Code hoch und schmal, für die Darstellung wurde aber auch ein quadratisches WebP-Beispiel bereitgestellt.
- Im Bild waren die hellen oberen und unteren Bereiche Text und Code, der schraffierte Bereich bei etwa einem Fünftel ein Diagramm und die meisten dunklen Bereiche Text im Diagramm.
- Die tatsächliche Einsparung war begrenzt.
- Bisherige gzip-Seite:
88 KiB - gzip-Seite nach WebP-Anwendung:
83 KiB - Erwartetes Brotli:
69 KiB
- Bisherige gzip-Seite:
- Seit dem 15. April 2026 wurde die Seite auf eine unkomprimierte Version umgestellt, damit Firefox-Besucher keine beschädigten Inhalte sehen.
- Rust-Code, Korpus und weitere Dateien sind auf GitHub veröffentlicht.
1 Kommentare
Meinungen auf Hacker News
Wenn man die Latenz ignoriert, stimmt das wohl, aber in der Praxis scheint sich die Ladezeit nur um etwa 0,001 % zu erhöhen.
Der Größenzuwachs ist im Vergleich zur Round-Trip-Latenz bedeutungslos, und die Zeit für die Dekompression könnte größer sein als die Zeit, die man durch 55 KiB weniger Übertragung spart.
Ein interessantes Experiment, aber in diesem Fall wird die User Experience wahrscheinlich eher schlechter; die Geschwindigkeit dürfte fast gleich bleiben, während nur die Kompatibilität sinkt.
Das Problem ist: Es gibt zwar Leute wie den Autor des TFA, die sich bemühen, 100 KB auf 50 KB zu reduzieren, aber es gibt auch Seiten, die mir über Roaming-Daten ganz selbstverständlich zig MB an Bildern schicken, obwohl ich nur die Öffnungszeiten eines Restaurants sehen will.
Ressourcenbewusstsein existiert, ist aber leider sehr ungleich verteilt.
Wenn die Verbindung abbricht, ist alles verloren, und nicht einmal der bereits heruntergeladene Teil lässt sich lesen.
Bei normalen Verbindungen ist der Unterschied bedeutungslos; bei sehr langsamen oder instabilen Verbindungen, bei denen 50 KB wichtig sind, ist dieser Ansatz eindeutig schlechter. Ein interessantes Experiment, aber bitte nicht auf Websites einsetzen.
Symbols-2048-em%20Nerd%20Font%20Complete.woff2, die den Unterschied fast wieder aufhebt.Es wird wohl kein Faktor 2,5 sein, aber 0,001 % ist es auch nicht.
In verlustbehafteten Netzwerken könnte es einen Unterschied geben, aber sicher bin ich mir nicht.
Ich verstehe nicht, warum
readPixelskein Ziel von Fingerprinting-Schutz ist. Für mich ist das in Ordnung, weil man ja keine kaum sichtbaren Tippfehler über die ganze Seite verteilt.Der Teil, dass im gzip-komprimierten HTML nur die Styles und etwa 8 KiB am Seitenanfang liegen und nur der Content unterhalb des Viewports mit WebP komprimiert wird, erklärt, warum der Artikel nach einem beliebigen Satz plötzlich abgeschnitten war und eine leere Seite folgte.
Ich nutze LibreWolf, daher ist WebGL deaktiviert; zufällige Web-Games, die WebGL benötigen, verwende ich in Chromium. Mit aktiviertem WebGL funktionierte der Artikel gut, und ehrlich gesagt ist das eine ziemlich elegante Technik.
Das WWW sollte allgemein zugänglich sein, indem es bei gewöhnlichem HTML beginnt und schrittweise verbessert wird.
Brotli direkt im Webbrowser zu verwenden ist zwar möglich, hat aber natürlich Einschränkungen.
Ich halte den JS1024-Beitrag von 2022 [1] für die erste Demonstration dieses Konzepts, und es gibt auch Proof-of-Concept-Code für beliebige Kompression. Leider passte er nicht zum ursprünglichen Zweck, dem Size Coding.
Die wichtigste Einschränkung ist, dass man praktisch auf ASCII-Zeichen beschränkt ist und es aus offensichtlichen Gründen sehr empfindlich gegenüber dem Rendering-Stack ist. Derzeit scheint es in Firefox nicht zu funktionieren.
[1] https://js1024.fun/demos/2022/18/readme
[2] https://gist.github.com/lifthrasiir/1c7f9c5a421ad39c1af19a9c...
Man kann nur das WOFF2-Schriftdateiformat nutzen, für das Brotli ursprünglich entworfen wurde; um das auszunutzen, muss man aber eine vollständige Schriftdatei erstellen.
Moderne Browser bereinigen nicht vertrauenswürdige Schriftdateien in der Regel mit dem OpenType Sanitizer (OTS), weil es sehr gefährlich ist, sie direkt ins System zu laden. Man muss also eine WOFF2-Datei erstellen, die normal genug ist, um von OTS akzeptiert zu werden, aber dennoch die gewünschte Bytefolge enthält und extrahierbar macht.
Die Idee, nach vielen Fehlschlägen bei den Glyphenbreiten, also den Advances, zu landen, die als Folge von 2-Byte-Integern mit Vorzeichen nahezu ohne Einschränkungen codiert werden, ist fantastisch.
Chromium hat das lange blockiert, aber auch zstd kommt jetzt ins Web. Es ist endlich in Chrome gelandet, jetzt muss nur noch Safari nachziehen.
Ich entwickle Batch Compress (https://batchcompress.com/en) und habe vor Kurzem WebP-Unterstützung hinzugefügt; kurz darauf habe ich sie zum Standard gemacht.
Soweit ich weiß, erzeugten wir bereits die kleinsten JPEGs unter den Web-Kompressionstools, aber WebP kam nur auf etwa 50 % der JPEG-Größe. Nach dem Hinzufügen der Unterstützung war es eine einfache Entscheidung, es bald zum Standard zu machen.
Da die Seite ziemlich viele Nutzer hat, erwartete ich nach der Umstellung auf WebP als Standard einige Beschwerden, aber nach ungefähr einem Monat gab es nur eine einzige Anfrage oder Beschwerde zu WebP.
Inzwischen scheinen fast alle Tools und Browser WebP zu unterstützen. Ich habe zuletzt nur eine Website gesehen, bei der ein WebP-Bildupload nicht korrekt verarbeitet wurde und dadurch der nächste Schritt blockiert war; heutzutage wird es fast überall gut unterstützt.
Wenn man JPEG gut komprimiert und optimiert, sollte es nicht weit hinter WebP zurückliegen.
Man kann jederzeit ein WebP erzeugen, das fast genauso aussieht wie ein JPEG, und damit die Dateigröße reduzieren; dasselbe gilt aber auch, wenn man es erneut als nahezu gleich aussehendes JPEG komprimiert.
Das ist eine Eigenschaft aller verlustbehafteten Kompressionscodecs, und weil die Dateigröße mit steigender Qualität exponentiell wächst, sind Leute immer wieder überrascht, wie stark sich die Dateigröße schon durch einen winzigen, kaum sichtbaren Qualitätsverlust ändert.
WebP war berüchtigt für schreckliche Standardeinstellungen, die Details in dunklen Bereichen zerstörten.
Beim Blick in den Quelltext ist mir aufgefallen, dass in der doctype-Deklaration ein Leerzeichen fehlt. Die aktuelle Form ist falsch; dort muss ein Leerzeichen stehen.
Das Weglassen des Leerzeichens bei
DOCTYPEkann es kürzer machen.Streng genommen ist es kein gültiges HTML, löst aber trotzdem erfolgreich den Standards Mode aus.
Siehe: https://GitHub.com/kangax/html-minifier/pull/970 / https://HTML.spec.WHATWG.org/multipage/parsing.html#parse-er...
Ich verwende diesen Trick auch auf https://FreeSolitaire.win.
Sieht nach dem Ergebnis eines Minifiers aus, der möglichst viel entfernt [0].
0: https://github.com/KTibow/KTibow/issues/3#issuecomment-23367...
Ich habe diesen Trick früher schon einmal ausprobiert. Seltsamerweise erinnere ich mich nicht mehr, wofür, aber vermutlich wollte ich nur sehen, ob es möglich ist, und habe auch hier einen Kommentar hinterlassen: https://gist.github.com/gasman/2560551?permalink_comment_id=...
Ich habe auch einen alten Prototyp gefunden; das war wohl nur ein Test: https://retr0.id/stuff/bee_movie.webp.html
Heute nennt man solche Dinge mit Skript-Injektion wohl eher Erweiterungen als Add-ons, aber jedenfalls ist der Ansatz interessant, zunächst „Müll“ auszuliefern, dann JS anzuhängen und es an die Seite zurückzugeben.
Der Sicherheitsnerd in mir fragt sich, ob das bei nutzerbereitgestellten Daten wie Kommentarformularen angreifbar werden könnte.
Vielleicht könnte jemand eine Bytefolge für einen Kommentar finden, die nach der Kompression vor meinem Skript landet und zu einem
script-Tag wird, der ausgeführt wird.Denn das meiste, was WebP Lossless gegenüber PNG hinzufügt, betrifft nicht die Codierung, sondern die Modellierung; bei dieser Art Textkompression nutzt man aber nur den Codierungsteil von WebP.
Wenn man Google Fonts entfernt, würde sich die Ladezeit der Seite ebenfalls etwas verbessern, weil sie von einem entfernten Server geladen werden und ein zusätzlicher Handshake nötig ist.
Diese Seite ist zumindest im Sailfish-OS-Browser kaputt. Nach dem folgenden Absatz gibt es eine große leere Fläche:
„Alright, so we’re dealing with 92 KiB for gzip vs 37 + 71 KiB for Brotli. Umm…“
Trotzdem ist der Overhead von gzip- und Brotli-HTML-Kompression im Vergleich zu der Menge an JS, Bildern und Videos, die heutige Websites verwenden, nichts.
Persönlich mag ich dieses Format nicht besonders. Wenn ich ein Bild speichere und es als WebP gespeichert wird, wird es außer von Webbrowsern nirgends unterstützt, sodass ich es erst konvertieren muss, bevor ich es bearbeiten oder sinnvoll nutzen kann.
Es fühlt sich einfach so an, als würde einem ein zusätzlicher Schritt aufgezwungen.
Wenn die Unterstützung weiter zunimmt, wäre es aber wohl in Ordnung. Ein neues Format alle 20 Jahre kann man aushalten.
.webmdarf gerne verschwinden.