CSS Sprites – Einsparung an HTTP-Requests durch Kombination von Hintergrund-Bildern

Wie sich ja gewünscht wurde, möchte ich von nun an auch etwas mehr über die Performance von Client-Systemen in Bezug auf Webanwendungen schreiben. Genauer: Wie müssen Webseiten ausgeliefert werden, damit Sie möglichst schnell im Browser des Besuchers dargestellt werden können?
In diesem Beitrag soll es dabei um die CSS Sprites bzw. Image Sprites gehen. Die Yahoo-Präsentation hat ja bereits darauf hingewiesen und hier soll nun erklärt werden, wie es funktioniert.

Problemstellung
Ein Problem, weshalb Seiten oft lange Ladezeiten haben, ist, dass recht viele HTTP-Requests gemacht werden müssen. Da HTTP ein zustandsloses Protokoll ist, muss für jedes Bild, jede JS-Datei und allgemain jede externe Ressource eine neue HTTP-Verbindung zwischen Browser und Server eröffnet werden. Das wäre ja noch nicht problematisch (lediglich umständlich). Das Problem ist aber, dass je nach Browser nur eine bestimmte Anzahl an parallelen HTTP-Requests ausgeführt werden können (oft 2-4). Im Firefox kann diese Einstellung über network.http.max-persistent-connections-per-server auf der about:config-Seite verändert werden.
Dadurch entsteht ein Treppen-Diagramm, das die Gesamtladezeit verdeutlicht: Treppeneffekt beim Laden mehrerer externer Ressourcen
Es wird deutlich, dass einige Beschleunigung des Ladens erreicht werden kann, wenn entweder die Anzahl paralleler Requests erhöht wird oder die Anzahl an Requests verringert wird. Ersteres kann beispielsweise durch unterschiedliche Subdomains gemacht werden (eine für Bilder, eine für JS usw.). Genaueres dazu findet sich beim Beitrag, wenn man auf den Link des Diagramms klickt.
Wir wollen uns jetzt aber mal mit dem anderen Punkt beschäftigen: der Verringerung der HTTP-Requests.

HTTP-Requests verringern könnte man auf 2 Arten: Mehrere Komponenten innerhalb einer Datei konsolidieren (ich hasse dieses Wort, seitdem es in jeder IT-Zeitung steht) oder die Komponenten inline in das HTML-Dokument einbinden. Letzteres geht beispielsweise gut bei CSS (wird z.B. auf Yahoo gemacht) aber auch bei Bildern (aber dann wird der Quellcode richtig hässlich).
Hier soll es um die Konsolidierung mehrerer Komponenten gehen, genauer um das Verringern von Anfragen für Hintergrundbilder.

Jedes mit CSS über url() referenzierte Hintergrundbild benötigt natürlich auch einen HTTP-Request. Aber habt ihr euch mal alte Spiele angesehen (bei aktuellen weiß ichs nicht, dafür hab ich leider kaum noch Zeit)? Dort sind meistens verschiedene GUI-Elemente in einer einzigen Bilddatei zusammengefügt. Hinterher schneidet sich dann das Programm den gewünschten Teil raus. Das spart Speicherplatz und Ladezeit.

Übertragen auf die Webentwicklung ist es ganz ähnlich: Mehrere Hintergrund-Bilder können in eine Grafik-Datei gepackt werden und später trotzdem an der richtigen Position angezeigt werden.
Der erste Schritt dafür ist das Zusammenpacken in eine Datei. Das geht ganz gut mit einem CSS-Sprite-Generator. Dabei sollte allerdings dringend darauf geachtet werden, dass man entweder nur Bilder nimmt, die später horizontal wiederholt werden (repeat-x) oder nur Bilder, die vertikal wiederholt werden (repeat-y) (bei mir traf ersteres dazu, deshalb kann ich die Tauglichkeit des verlinkten Generators für Letzteres nicht einschätzen). Zu den Gründen unten mehr.
Jedenfalls spuckt der Generator letztlich eine Bilddatei aus, in der alle eure Hintergründe enthalten sind. Dazu gibts noch einen CSS-Code.

Allein mit den Anweisungen dort beim Generator habe ich es allerdings nicht hinbekommen, die Sprites zum Laufen zu bekommen, sondern mir war noch dieser Beitrag behilflich. Zuerst muss die Sprite-Grafikdatei nämlich einmal eingebunden werden. Und zwar am besten für alle CSS Regeln, bei denen eines der im Sprite enthaltenen Bilder als Hintergrundbild genutzt wird. (Beispiel siehe unten)

Anschließend kann man sich um das Ersetzen der alten Hintergrundregeln kümmern, denn die werden nun nicht mehr benötigt. Je nachdem, welche CSS-Regeln obiger Generator ausspuckt, ersetzt ihr background-image nun nur noch durch background-position (also inklusive den vom Generator ausgepuckten Werten). Dadurch wird das Sprite an die für das bestimmte Element korrekte Position verschoben.

Nun aber zum repeat. Wenn ihr bisher die Multiformat-Eigenschaft background benutzt habt, müsst ihr das jetzt aufdröseln, denn es wird ja nur noch background-position und background-repeat benötigt. Jedenfalls werdet ihr eventuell schnell merken, dass wenn ihr repeat-x verwendet, es kein hübsches Hintergrundbild wie früher gibt. Der Grund dafür sind unterschiedlich breite Hintergrundbilder. Dadurch entstehen mitunter neben dem Bild weiße (oder transparente; je nachdem, was man einstellt) Ränder. Für ein repeat-x werden aber durchgängige Bilder benötigt.
Deshalb gibt es 2 Wege dieses Problem zu beheben: Entweder alle Bilder nachträglich auf gleiche Breite ziehen. Oder (von mir präferiert) einfach nur den ersten Pixel in der Breite des Sprite-Bildes verwenden (bzw. gleich nur 1px breite Bilder dem Sprite-Generator geben). Kann man ganz einfach per Irfan View oder auch irgendeinem anderen Grafikprogramm auswählen, freistellen / kopieren und entsprechend abspeichern. Es ist sowieso nur ein 1px breites Bild erforderlich, da der Rest ja per repeat-x generiert wird.

Und jetzt kommt noch das zweite Problem, auf das ich gestoßen bin: Ich habe einen Seiten-Hintergrund, der 500px hoch ist (Verlauf). Wenn die Seite länger ist, wird einfach die angegebene Hintergrundfarbe angezeigt. Wenn nun aber unterhalb dieses Bildes im Sprite noch ein völlig anderer Hintergrund kommt, weiß CSS natürlich nicht, wo es das Bild beenden soll (insbesondere da man per CSS zwar das Hintergrundbild verschieben nicht aber einen bestimmten Bereich ausschneiden kann). Entsprechend wird dann einfach der darunterliegende Hintergrund noch mit angezeigt. Entweder man wählt zur Behebung dieses Problems sehr große vertikale Abstände zwischen den einzelnen Hintergründen (diese Lösung ist allerdings nicht sehr flexibel, denn eventuell wird in der Zukunft eine noch längere Seite erstellt, wo der Abstand dann nicht mehr ausreicht) oder man wählt für das Sprite nur Hintergrundbilder von Elementen, deren Höhe feststeht (per CSS-Attribut height; teilweise auch bei Inline-Elementen, jedoch muss da eventuell auf die Schriftgröße geachtet werden – ist diese nämlich relativ angegeben, ist auch die Höhe des Elements variabel).

Nun noch ein kleines Beispiel zur Verdeutlichung:
Alte Regeln:

#header {background: url(bild1.jpg) top left repeat-x; height:80px;}
ul.menu li {background: url(bild2.jpg) top left repeat-x; height:15px;}

bild1.jpg und bild2.jpg sind jeweils 1px breit. Durch den Sprite-Generator jagen, Sprite-Datei runterladen bzw. auf Webserver hochladen.

Sprite-Bild einmalig einbinden für alle Elemente, die einen im Sprite enthaltenen Hintergrund haben:

#header, ul.menu li {background-image:url(bg_sprite.png);}

Mit Hilfe der im Generator angegebenen Regeln die alten Regeln ersetzen:
Generator spuckt zum Beispiel aus:

.sprite-bild1 { background-position: 0 -30px; } 
.sprite-bild2 { background-position: 0 -110px; }

Einbau in vorhandenes CSS:

#header {background-position: 0 -30px; background-repeat: repeat-x;}
ul.menu li {background-position: 0 -110px; background-repeat: repeat-x;}

Und das wars. Mit 2 Bildern macht das noch nicht so viel Sinn, aber ich denke das reicht zum Verdeutlichen.

Falls ihr noch Fragen habt zu diesem Thema, freue ich mich über Kommentare. Euren Erfolg durch diese Maßnahme prüfen könnt ihr übrigens mit Firebug (Tab Net) sowie YSlow (Tab Performance).

Jan hat 149 Beiträge geschrieben

37 Kommentare zu “CSS Sprites – Einsparung an HTTP-Requests durch Kombination von Hintergrund-Bildern

  1. SeeeD sagt:

    Toller Artikel..
    Gerade die Stelle wie man die http-Requests erhöhen kann fand ich super.
    Werde ich direkt ausprobieren und schauen ob das Laden schneller und flüssiger abläuft :)

  2. Mira sagt:

    Wow du hast dir echt viele Gedanken zum Thema gemacht. Gerade von der Idee, Bilder und JS auf verschiedene Domains auszulagern bin ich recht begeistet. Auch heutzutage haben nur ca 50% alle User DSL, da ist die Ladezeit-Optimierung durchaus relevant.

  3. Robert sagt:

    Da ich auch meine HTTP Request verringern möchte, bin ich auf Deinen Artikel getstoßen. Sehr gut beschrieben, da werde ich mich mal an die Arbeit machen….

  4. Xian sagt:

    Wenn man mehrere Buttons mit gleicher oder ähnlicher Breite hat, ist diese Idee sicherlich sinnvoll. So habe ich alle 3 Zustände meiner Buttons (normal, hover, aktiv) in je eine einzelne Grafik gebracht.

    Für 1 px breite und x px hohe Hintergrundbilder oder Bilder mit stark abweichenden Maßen finde ich diese Technik aber eher ungeeignet; eben aus den von dir beschriebenen Problemen.

  5. Mick sagt:

    Spriteme.org ist das einfachste und meiner Meinung nach beste Tool. Am besten danach die Bilder noch durch einen Crusher schicke, so konnte ich die Ladezeit (mit anderen Optimierungen) von 3-4 auf knapp 1 Sekunde drücken, ohne, dass die Seite schlechter aussieht oder sonstwas :)

  6. Danke für die Erklärung, jetzt habe ich es wenigstens verstanden und kann es auf meiner Webseite umsetzen. Mal schauen mit welchen der Programme ich am besten umgehen kann.

  7. Hallo,

    ich habe mir vorhin mal CSS Sprites gebaut und die Ladezeit ist meines Empfinden nach etwas schneller geworden. Google hat ja ein Site Speed-Tool und wenn ich ohne Sprites die Seite bei dem Tool teste, lädt meine Website 0,3 Sek. schneller als mit Sprites. Habe das 3x getestet.

    Woran liegt das? Soll ich sie trotzdem verwenden?

  8. Pingback: Wordpress Tuning
  9. DieterM sagt:

    Vielen Dank für diesen Beitrag, hat mir sehr weitergeholfen da ich vorallem fpr Mobile Nutzer versuche die Ladezeiten so gering wie möglich zu halten.

Eine Antwort schreiben

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

Du kannst folgende HTML-Tags benutzen: <a href=""> <blockquote cite=""> <pre lang=""> <b> <strong> <i> <em>