CSS-Tuning mittels DATA-URI (base64)

Dieser Artikel soll als Ergänzung zum Artikel CSS-Sprites dienen, den Jan vor einem Jahr veröffentlicht hat. Ergebnis soll so sein: Internetwerbung (Quellcode der CSS-Datei)
Wie Jan bemerkt hat, ist die Verwendung von CSS-Sprites teilweise sehr tricky und liefert z.b. schlechte Ergebnisse wenn:

  • die kombinierten Bilder verschiedene Farbtiefen haben, weil das Ergebnisbild entweder die größte Farbtiefe aller Bilder hat und dadurch sehr groß wird, oder die kleinste Farbtiefe aller Bilder hat und dadurch eher unansehnlich wird. Zwischenwerte sind denkbar aber auch immer nur ein Kompromiss zwischen Qualität und Dateigröße
  • Angenommen man möchte 2 Bilder kombinieren: 1.) 1x1000px und 2.) 1000x1px, dann wird das Ergebnisbild entweder 1001x1000px oder 1000x1001px groß, d.h. was man durch Verringerung an HTTP-requests einspart, zahlt man durch zusätzliche Übertragungsdaten wieder drauf
  • das Problem mit Repeat bei sehr langen Seiten
  • beim Einfügen neuer Teilbilder muss ggf. das ganze “Sammelbild” neu zusammengesetzt werden (inkl. Änderungen in der CSS-Datei).

Die Lösung heißt DATA-URI (base64)

Die Grundlagen dazu sind sehr gut in der englischen Wikipedia nachzulesen. Allerdings möchte ich euch einige (selbstentwickelte) Tuningmaßnahmen vorstellen. Zuerst muss gesagt werden, dass man mit dieser Methode sämtliche HTTP-Requests aus seiner CSS-Datei entfernen kann. Dadurch wird die CSS-Datei allerdings um die Summe aller verwendeten Bilder größer. Deshalb unbedingt Cache-Header verwenden!!! Da meine “Tuningmaßnahmen” sehr serverlastig sind (Bildbearbeitung mittels PHP), empfehle ich dringend die Ergebnis-CSS-Datei zu cachen: Ich habe einen einfachen (aber wirkungsvollen) Cache implementiert.

So solls am Ende aussehen

Auf einer meiner Internetwerbung Seiten (die Seite ist egal, und dient nur Demonstationszwecken) kann man sich die CSS-Datei ansehen. Wenn man den Quelltext nach der Zeichenkette: “url(“ durchsucht, sieht man alle verwendeten Bilder als Teil der CSS-Datei und nicht mittels Link-Tag referenziert. Der Kommentar am Anfang ist nur informativ für mich und sagt mir die Zeichenzahl meiner CSS-Datei (=Größe).

Anleitung zum Umbau der CSS-Datei

Wer erstmal “nur” testen möchte, kann gerne die Code-Schnipsel von Wikipedia verwenden. Ich habe diese weiterentwickelt und stelle sie euch hier exklusiv zur Verfügung.

  • CSS-Datei irgendwo sichern … man weiß ja nie 🙂
  • Falls noch nicht in der .htaccess vorhanden: Addtype application/x-httpd-php .css einfügen, damit die CSS-Datei durch den PHP-Interpreter läuft (alternativ kann auch die style.css in style.css.php umbenannt werden, aber dann müssen auch alle Referenzen im HTML-Code geändert werden)
  • Dem Root-Verzeichnis Schreibrechte 777 geben (aus Sicherheitsgründen nach Erzeugung der CSS-Datei die Schreibrechte wieder entfernen)
  • In der Index-Datei den Pfad zur CSS-Datei ändern in: style_neu.css
  • Folgenden PHP-Code an den Anfang der CSS-Datei einfügen: CSS-Code für base64-Codierung
  • Die alte CSS-Datei aufrufen, damit die neue CSS-Datei erstellt wird.
    Was Passiert? :
    Die bisherige CSS-Datei wird umgebaut und liefert die neue komprimierte CSS-Datei mit eingefügten Base64-Bildern. Die neue CSS-Datei wird unter dem Namen style_neu.css im Root-Verzeichnis gespeichert (ggf. kann man das anpassen (bedenkt: Schreibrechte 777 für den betroffenen Ordner setzen)). Die ganze Umwandlung besteht aus 3 Schritten:
    ob_start(‘compress’);
    Der Ausgabepuffer wird aktiviert und komprimiert die bisherige CSS-Datei indem überflüssige Zeichen entfernt werden. Bemerkung: Falls jemand eine Methode (Code-Schnipsel) kennt, wie man die CSS-Datei noch weiter verkürzen kann (CSS-Shorthand-Erkennung, Zusammenfassung gleicher Parameter (wie in meinem CSS-Beispiel), u.ä.), bitte kurze Nachricht an mich (die Geschwindigkeit der Umwandlung ist egal, weil das Ergebnis gecached wird).
    function data_url
    Diese Funktion ist das eigentliche Herzstück. Sie wird für jedes Bild der CSS-Datei aufgefufen. Pflichtparameter sind: die Bild-Url ($file) und der MIME-Datentyp ($mime). Optionale Parameter sind die Seitenlängen des Bildes (damit das Bild, falls es nur verkleinert gebraucht wird, vor der Umwandlung in Base64 gestaucht werden kann) und die Qualität (0 <= X <= 100). Die ersten Zeilen legen fest, ob sich eine Bildbearbeitung lohnt. Ich lege fest, dass kleine Bilder (kleiner als 70 x 70px) prinzipiell Base64-Codiert werden sollen, weil bei diesen Bildern die HTTP-Requestzeit im Verhältnis zur eigentlichen Übertragungszeit sehr hoch ist. Dateien werden durch die Base64-Codierung ca. 1/3 größer. Dadurch kann es passieren, dass bei großen Bildern die Übertragungszeit stärker wächst, als die Zeiteinsparung durch Verringerung der HTTP-Requests. Und deshalb lasse ich diese Bilder lieber normal referenziert. Außerdem möchte ich Bilder codieren, die in der fertigen CSS-Datei (um mindestens 30%) kleiner sind als das Originalbild, weil die Einsparung durch die physische Verkleinerung des Bildes den zusätzlichen Traffic durch die Base64-Codierung wett macht.
    Ähnlich verhält es sich bei Bildern, die lediglich in geringer Qualität gebraucht werden (z.B. bei sehr kontrastarmen Bildern merkt man Qualitätsunterschiede kaum). Alle Bilder, die nicht ausgewählt wurden, werden wie bisher normal referenziert. Alle anderen (also die meisten der CSS-Datei) werden je nach Mime-Typ gestaucht. Die Verwendung des Ausgabepuffers in der Funktion ist ein Trick, damit man das Bild als String bekommt, den man dann in Base64 umwandeln kann. Die restlichen Funktionen sind reine Arbeitsfunktionen ohne (geistige) Leistung meinerseits.
    Schreiben-Funktion
    Wie man sich schon denken kann, übernimmt diese Datei den Schreibvorgang der fertig erzeugten CSS-Datei. Es wird noch der Header: Content-Type: text/css eingefügt und festgelegt, dass die Style_neu.css komprimiert an den Browser versendet wird. Das lohnt sich sehr, weil Base64-Code sehr stark komprimiert werden kann.
  • alle Stellen der CSS-Datei, an denen ein Bild mittels url(‘xxxxx.yyy’) referenziert wird, werden ersetzt durch url([Base64-Code]), wobei Breite und Höhe unbedingt angegeben werden sollten, und den optionalen Parameter Qualität zwischen 1 und 100 (einfach mal ausprobieren, wie weit man die Qualität runterschrauben kann, bis man den Unterschied sieht).
  • kontrollieren, dass jedes externe Bild in genau einer CSS-Regel verwendet wird (notfalls Regeln neu sortieren), damit das selbe Bild nicht mehrfach in die CSS-Datei eingefügt wird. Beispiel: Bisher:
    .a{
       height:18px;
       margin:0 0 0 5px;
       padding:0px;
       background-image:url('a.jpg');
       }
    .b{
       height:20px;
       margin:0 0 0 5px;
       padding:0px;
       background-image:url('a.jpg');
       }
    .c{
       height:22px;
       margin:0 0 0 5px;
       padding:0px;
       background-image:url('a.jpg');
       }

    Also 3 CSS-Regeln, die sich nur in verschiedenen Height-Werten unterscheiden.
    Müssen umgewandelt werden in:

    .a,.b,.c
       {
       margin:0 0 0 5px;
       padding:0px;
       background-image:url(<?=data_url('a.jpg','image/jpg',10,10)?>); /*angenommen breite und höhe sind 10px*/
       }
    .a{
       height:18px;
       }
    .b{
       height:20px;
       }
    .c{
       height:22px;
       }

    Ergebnis: das Hintergrundbild ist jetzt nur noch in einer Regel und wird deshalb auch nur einmal in die CSS-Datei eingefügt. Kleiner Nebeneffekt: die redundanten margin- und padding-Werte wurden entfernt.

Benchmark

Ich habe auf meiner Seite beide Varianten mit YSlow getestet:
Vorher: 0,56s
Nachher: 0,79s (css nicht gecached (umwandlung on the fly))
Nachher: 0,25s (css gecached)

Fazit

Im ungecachetem Zustand wird die Zeiteinsparung durch die Verringerung der HTTP-Requests vollständig durch die Erstellungszeit der neuen CSS-Datei aufgebraucht. Ein Cache ist also unbedingt notwendig!

Nur nebenbei

Jan hatte ja schon erwähnt, dass die meisten Browser eine interne Beschränkung haben, was die Anzahl der gleichzeitigen HTTP-Requests angeht. Erwähnenswert ist, dass die meisten Betriebssysteme ebenfalls eine interne Beschränkung haben. D.h. es ist doppelt wichtig die Anzahl der Requests der eigenen Seite gering zu halten, weil es eben 2 Flaschenhälse gibt.

Nur Nebenbei II

Wer noch die letzten Prozente rausholen möchte, kann die gecachede Datei gleich gezipt speichern, damit beim Ausliefern die Zeit zum Zippen entfällt. Bei mir bringt das etwa 0,05s … (klingt wenig, entlastet den Server aber)

Ein kleines Quiz am Ende

Was wird hier gemacht?

echo '<link rel="icon" href="data:image/ico;base64,'.base64_encode(file_get_contents('images/favicon.ico')).'" />';

Martin-Kiesewetter hat 4 Beiträge geschrieben

19 Kommentare zu “CSS-Tuning mittels DATA-URI (base64)

  1. Jan sagt:

    Kleine Anmerkung: Der PHP-Code in der dynamisch geschriebenen CSS-Datei funktioniert nur, wenn die php.ini-Einstellung short_open_tag auf On bzw. 1 steht.
    Andernfalls muss aus allen ‘<?’ ein ‘<?php’ gemacht werden.

  2. Christian sagt:

    Wie schaut die Sache im IE kleiner als Version 8 aus? Da funktioniert das Ganze laut Wikipedia ja nicht. Werden dann einfach keine Bilder angezeigt?
    Wäre schade, weil die Methode eben durch Verringerung der Anzahl der HTTP-Requests Zeit einsparen könnte.

  3. Ulf sagt:

    Zum Quiz:
    Das Fav-Icon wird gelesen und dann nach base64 kodiert. Schlussendlich wird der base64-kodierte Content auf den Response-Stream geschrieben.

    Sieht also so aus als ob das Fav-Icon in Base64 im HTML-Quelltext gespeichert wird.

    Viele Grüße
    Ulf

  4. @Jan: Zwinker, Zwinker 🙂
    @Ulf: Natürlich Richtig! … mit diesem mini-aufwand erstpart man sich den HTTP-Request für das Favicon. Natürlich gilt auch hier die Überprüfung der Browserversion…

    Grüße!

  5. Zoran Zaric sagt:

    kannst du bei gelegenheit noch einen weiteren test machen?
    leerer browsercache aber css datei schon erstellt
    ich könnte mir vorstellen, dass die große css datei mit den integrierten bildern schwerer für den browser zu parsen ist.

    Grüße,
    Zoran

  6. die Tests sind alle mit leeren cache gemacht…sonst wärs ja sinnlos, weil die css-datei (die ja 90% der Ladezeit verursacht) schon im client-browser wäre, und dadurch nicht nachgeladen werden müsste…
    ==>mit gefülltem Cache sollte sich sich diese variante zur Herkömmlichen kaum unterscheiden, weil bei beiden varianten eh schon alles im Cache liegt (bei der einen die css-datei, bei der anderen die (kleinere) css-datei + css-bilder

    Gruß!

  7. GhostGambler sagt:

    Was bei dieser Variante natürlich noch wichtiger ist als sonst, da die CSS-Datei durch die eingebundenen Bilder je nach Verwendung wenig bis deutlich größer werden, ist, dass die CSS-Datei dann auch entsprechend vom Browser gecached wird.
    Da PHP prinzipiell dafür sorgt, dass die geparsten Dateien überhaupt nicht gecached werden, muss man die entsprechenden Header dann selbst (möglichst korrekt) mitsenden. Dies wird natürlich zusehends komplizierter, je mehr Dateien in einer Datei vereint werden (man muss überprüfen, ob sich *irgendeine* dieser Dateien seit dem letzten Aufruf geändert haben).
    Ansonsten werden die Seiten zwar schneller ausgeliefert, dafür hat man das x-fache an Volumen. Das erachte ich als schlechter als die vorige Variante.

    Darum muss man sich bei der vorigen Variante übrigens meist auch nicht extra kümmern, da sowohl Bilder wie auch CSS-Dateien von allen vernünftigen Servern automatisch mit den entsprechenden Cache-Headern versehen werden.

  8. HO sagt:

    Wie schaut die Sache im IE kleiner als Version 8 aus? Da funktioniert das Ganze laut Wikipedia ja nicht. Werden dann einfach keine Bilder angezeigt?
    Wäre schade, weil die Methode eben durch Verringerung der Anzahl der HTTP-Requests Zeit einsparen könnte.

  9. welche Browser im detail dieses “Feature” unterstützen kann ich spontan nicht sagen…wenn das wikipedia sagt, dann muss das ja stimmen 🙂
    Ich machs in meinen Projekten so, dass ich explizit nur bei “Browser=Firefox” data-uri verwende…und decke damit schon den größten Bereich der Nutzer ab…

  10. zod sagt:

    Ich habe es mit einer PNG gemacht die zuvor schon durch pngquant optimiert war. Resultat war ein Traffic zuwachs von ca. 33% unkomprimiert bzw. 5% komprimiert!

    Ist der größere Traffic wirklich vertretbar gegenüber einen einzelnen eingesparten HTTP-Requests?

    Man bedenke, das es immernoch Browser gibt die kein gzip mitmachen.

  11. das ganze lohnt sich primär bei vielen kleinen Bildern…und du kannst ja per PHP entscheiden, ob du diese optimierung machen willst (anhand der Information, ob der anfragende browser gzip kann)

    Grüße

  12. Andreas sagt:

    So etwas ähnliches wurde schonmal gemacht, inkl. der angesprochenen Unterscheidung für Browser.

    Das Tool SmartOptimizer (http://farhadi.ir/works/smartoptimizer) automatisiert oben angegebene Funktionen durch einen einfachen .htaccess Eintrag (und natürlich der Installation des Tools).
    Danach Minified das Script alle CSS und Javascript Files, liefert sie, wenn der Browser es unterstützt GZipped aus, und liefert die base64 Kodierung in Abhängigkeit des Browsers aus.

    Zusätzlich bietet es Unterstützung für folgende Syntax
    , diese Dateien werden denn mit einem Request ausgeliefert. (Das ist natürlich keine HTML-Erweiterung, sondern funktioniert auch über htaccess)

    Das Gleiche geht mit JavaScript Dateien.

  13. Daniel sagt:

    Hallo, ich wollte gerade mal das “kleines Quiz am Ende” testen…und muß sagen bei mir geht das nicht, das favicon ist im FF nicht mehr zu sehen und der html-quelltext sieht bei mir so aus:

    den php-code habe ich so eingefügt:
    <?php echo '’;?>

    Was mache ich da falsch, oder ist ein fehler in eurem Beispiel?

  14. Daniel sagt:

    Nachtrag, hab grad gesehen das wird alles entschärft…ich probiers nochmal.

    html-quelltext sieht bei mir so aus:

    den php-code habe ich so eingefügt:
    < ? php echo '’;? >

Eine Antwort schreiben

Ihre E-Mail-Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert mit *

You may use these HTML tags and attributes: <a href=""> <blockquote cite=""> <pre lang=""> <b> <strong> <i> <em>