Performance-Optimierungen in CSS – ja, das geht!

Wenn es um die Performance einer Website geht, so denkt man schnell an JavaScript, Requests und an Komprimierung. Dass man aber schon bei der initialen Gestaltung damit anfangen kann, etwas zu optimieren, beachten viele Entwicklerinnen und Entwickler gar nicht. In den vergangenen Jahren haben sich viele Praktiken eingebürgert, die eine gute Basis für das Ausliefern von CSS mit sich bringen. Weil der Fokus bei der Entwicklung aber oft auf anderen Bereichen liegt, machen sich die wenigsten Gedanken darüber, was sie alles an ihrem CSS optimieren können.

Um ein Gefühl dafür bekommen zu können, wie man das eigene CSS verbessern kann, sollte man zunächst grob verstehen, wie ein Browser selbiges interpretiert und rendert. Findet er auf einer Website eine CSS-Datei, so wird diese komplett geladen, bevor der Parser des Browsers sie interpretiert. Damit verhindern die Browser das frühzeitige Ändern von Werten eines Elements, da erst nach dem vollständigen Laden klar ist, welche Deklaration letztendlich aktiv ist. Dazu muss man wissen, dass sich Deklarationen in CSS gegenseitig überschreiben können, es ist nämlich nicht möglich, dass ein Element beispielsweise zwei Hintergrundfarben besitzt.

An einem Beispiel würde das folgendermaßen aussehen:

body {
color: #333;
}
body {
color: #666;
}

Hier erhält der body einmal die Farbe #333 und einmal die Farbe #666. Da die Regel mit der Farbe #666 später im Code zu finden ist, wird die erste Anweisung mit der Farbe #333 ignoriert.

Dabei ist in der Praxis ein Überschreiben von Werten eines Elements innerhalb einer CSS-Datei ohne große Performance-Einbußen möglich. Erst das Überschreiben innerhalb von anderen CSS-Dateien, in-site oder inline verschlechtert die Performance deutlich. Platziert man beispielsweise eine CSS-Datei mit einer Größe von rund 25 KiB doppelt in seine Website, benötigt der Browser in meinem Testfall über 50 ms, allein um die zweite Datei zu interpretieren und die Regeln des CSS korrekt anzuwenden. Die erste Datei brauchte indes nur 6 ms.

Übrigens …

JavaScript kann ebenfalls Abfragen über das Aussehen eines Elements durchführen, was unter Umständen zu einer Verzögerung der Ausführung besagten JavaScript-Codes führt. Der Browser muss nämlich zuerst das CSS interpretieren, um darauf aufbauend eine korrekte Antwort zu liefern. Während Firefox sämtliches JavaScript blockiert, solange CSS geladen wird, machen beispielsweise Chrome und Safari das nur dann, wenn das JavaScript eine solche Abfrage über das Aussehen vornimmt.

!important ist böse

Jeder, der sich mit CSS etwas auseinandergesetzt hat, sollte mir zustimmen können, wenn ich sage, dass die Nutzung von !important immer schwierig ist und man darauf verzichten sollte. Rein aus konzeptioneller und logischer Sicht gibt es bereits mehr als genug Gründe gegen !important. Einige davon sind:

  • Die Kaskadierung in CSS, das namensgebende Konzept der Cascading Style Sheets, wird umgangen.
  • Der Code wird unnötig kompliziert.
  • Deklarationen mit !important können nur schwer überschrieben werden.
  • Ohne !important muss man seinen Code mit sinnvollen Selektoren schreiben, um die Eigenschaften für diese auch wieder überschreiben zu können, falls notwendig.

Doch warum gibt es auch Gründe aus Performance-Sicht, die gegen die Nutzung von !important sprechen?

CSS funktioniert über eine Spezifität, d. h. je spezifischer der Selektor, desto höher wird er bewertet und seine Regeln erhalten ein höheres Gewicht, sodass sie eher beachtet werden. !important nutzt jedoch keine geregelte Spezifität, sondern funktioniert nach dem Schema „hier bin ich, ich bin viel wichtiger, beachte nur mich“. Da erst der Selektor selbst nach der Spezifität geprüft wird, ist es so für den Browser sehr viel aufwendiger, den korrekten Wert zu interpretieren, da das !important erst auf Ebene des Eigenschaftswertes zu finden ist. Dadurch muss der Browser dem Wert eine andere Beachtung schenken, als eigentlich angenommen. Es muss demnach erst der Selektor, dann der Eigenschaftsname und dann der Eigenschaftswert (nachfolgend #333) beachtet werden:

.selector {
	color: #333;
}

Damit möchte ich aber nicht sagen, dass !important überhaupt nicht eingesetzt werden kann, soll oder darf. Es sollte immer nur mit Bedacht genutzt werden und nicht, nur weil es einfacher ist.

Gib @import keine Chance

Wer den Artikel bisher aufmerksam durchgelesen hat, kann sich bereits denken, warum die Nutzung von @import keine gute Idee ist. Beispielhaft einmal folgender Code in einer Datei namens first.css:

@import url("second.css");
body {
	color: #333;
}

Im Gegensatz zu einer Angabe beider CSS-Dateien direkt als link-Element innerhalb der HTML-Datei wird hier zuerst die Datei first.css komplett heruntergeladen und interpretiert, bevor die Datei second.css geladen und interpretiert wird. Das führt zwangsläufig zu einem langsameren Ablauf des Downloads der Dateien als ein parallelisierter innerhalb von HTTP-Requests.

Die Nutzung als link-Element sieht dabei in der HTML-Datei folgendermaßen aus und ist die zu nutzende Variante:

<html lang="de">
<head>
<link rel="second.css"/>
<link rel="first-alternative.css"/>

Hier wird nun wirklich zuerst die second.css abgearbeitet und dann die Datei first-alternative.css, die dann natürlich im Vergleich zur first.css oben das @import url("second.css"); nicht mehr besitzt.

Split ’em

Vor wenigen Jahren war es „best practice“, einfach sämtliches CSS in einer Datei unterzubringen und diese überall einzubinden. Die Vorteile:

  • Geringer Wartungsaufwand
  • Nur ein Request

Heutzutage gibt es jedoch viele Nachteile, die sich erst und insbesondere durch das mobile Internet sowie neue Standards herauskristallisierten:

  • Große Dateien blockieren die Bandbreite, insbesondere mobil
  • Unnötiges CSS muss auf verhältnismäßig schwachen Systemen interpretiert werden
  • HTTP/2 kann mehrere kleine Dateien schneller verarbeiten

Mittlerweile hat das Vorgehen, das gesamte CSS in eine einzelne Datei zu speichern, mehr Nachteile, die teils dramatische Auswirkungen haben können. Daher ist es sinnvoll, sein CSS so aufzuteilen, dass nach Möglichkeit immer nur der Teil geladen wird, der auf einer Seite auch wirklich benötigt wird. In der heutigen Zeit, in der man mit LESS- und SASS-Compilern genau sagen kann, wo welcher CSS-Teil gespeichert werden soll, kann man sich seine Entwicklungsumgebung mit sehr wenig Aufwand so einrichten, dass dies alles automatisiert geschieht.

Fonts, Fonts, Fonts

Mit dem Aufkommen von @font-face wurden ganz neue Schriftdarstellungen möglich. Endlich musste man sich nicht mehr auf langweilige Standard-Schriften beschränken, sondern hatte eine große Vielfalt. Leider verschlechtern zusätzliche Schriftarten die Performance einer Website immer verhältnismäßig stark, da sie oftmals nicht gerade klein sind und aufwendig vom Browser dargestellt werden müssen. Doch was haben Schriften überhaupt mit CSS zu tun? Nun, sie werden per CSS eingebunden, wodurch man direkt die Möglichkeit hat, einiges zugunsten einer hohen Performance zu beachten.

Mit Werkzeugen wie den Google Fonts hat man einfache Möglichkeiten, zusätzliche Schriften zu laden. Allerdings wird hier jedes Mal eine externe CSS-Datei mit wiederum externen Schrift-Ressourcen geladen.

Deutlich verbessern kann man die Performance hier bereits, wenn man die Schriften herunterlädt und vom eigenen System aus einbindet. Der faule Benutzer mag nun die Schriften einfach über seine bereits vorhandene CSS-Datei einbinden und hat damit auch schon etwas gewonnen, nämlich die zusätzlichen Requests zu Google gespart (welche auch nur schlecht im Cache zwischengespeichert werden), doch noch nicht das Optimum herausgeholt.

In der heutigen Zeit mit HTTP/2 sollte man eher mehrere kleine Requests durchführen als wenige große. Daher empfehle ich, eine separate CSS-Datei zu erstellen, welche lediglich die @font-face-Anweisungen besitzen. Diese Datei sollte dann vor jener CSS-Datei (bzw. am besten immer direkt als erstes) geladen werden, die das Design beschreibt. Durch ihre Größe wird sie schnell geladen und interpretiert, gleichzeitig kann aber der Browser bereits die Schrift laden und interpretieren, während er zeitgleich bereits die nächste(n) CSS-Datei(en) lädt. Die Anzeige der Schrift wird dadurch beschleunigt. Das kommt natürlich aber auf die Serverkonfiguration an und bietet unter HTTP/1.1 keinen Mehrwert, sondern ist dort kontraproduktiv.

OpenType Variable Fonts

Möchte man die Schriften zusätzlich optimieren, bietet sich die Nutzung von sogenannten „OpenType Variable Fonts“ an. Im Normalfall muss nämlich für jeden Schrifttyp (fett, kursiv, fettkursiv etc.) jeweils eine eigene Schriftdatei geladen werden. Das ist mit Variable Fonts anders. Hier sind die Informationen für die verschiedenen Schrifttypen in einer Datei vereint. Dadurch verringert sich die übertragene Dateigröße dramatisch, wenn man mehrere Schrifttypen einsetzt.

Font Subsetting

Eine weitere Form, wie man die Datenmenge verringern kann, die mit individuellen Schriften aufkommt, nennt sich „Font Subsetting“. Da Schriften möglichst global eingesetzt werden sollen, beinhalten sie auch sehr viele verschiedene Zeichen und Varianten. Diese braucht man unter Umständen gar nicht. So zum Beispiel Kapitälchen (small caps), alternative Zeichen-Varianten oder spezielle Zeichen. Diese kann man beim Font Subsetting aus der jeweiligen Schriftdatei sparen und so nur die Zeichen einbinden, die man auf der eigenen Website auch wirklich benötigt. Dann kann schnell die Dateigröße um 30 % und mehr verringern.

In-site CSS

Auch wenn man sehr schnell gelernt hat, CSS nur in externen Dateien zu nutzen – weil es nur dorthin gehört – ist mein nächster Tipp, genau das nicht zu tun. Zumindest nicht für den Teil, der „above the fold“ angezeigt wird, also alles, was (insbesondere mobil) ohne zu scrollen direkt sichtbar ist. Ist dieser Code direkt in der HTML-Datei vorzufinden, muss keine zusätzliche Datei über einen neuen Request geladen werden. Das CSS kann sofort interpretiert werden.

Gerade auf mobilen Geräten führt das zu einer stark beschleunigten Anzeige des ersten Inhalts.

Mobile-First-Ansatz

Der Mobile-First-Ansatz ist heute wichtiger denn je, da über die Hälfte des Traffics mobil verursacht wird. Dabei wird, ohne @media-Queries, eine voll funktionale mobile Variante der Website gestaltet. Anschließend werden selbige dann genutzt, um zum Beispiel für Tablets oder Desktop-PCs zusätzlich notwendige Anpassungen vorzunehmen.

Die Idee dahinter ist, dass mobile Geräte weniger Leistung besitzen, um unter anderem das CSS zu interpretieren und dementsprechend gar keine @media-Queries interpretieren müssen. Je größer ein Gerät ist, desto mehr Leistung hat es im Normalfall auch (ein iPad hat beispielsweise mehr Leistung als ein iPhone), sodass dort zusätzliche @media-Queries weniger ins Gewicht fallen.

CSS-Frameworks und -Resets

Es gibt viele CSS-Frameworks und CSS-Resets, also bereits vordefinierten CSS-Code, welcher je nach Framework bereits unterschiedliche Funktionen bietet. Das ist natürlich für jemanden, der ein solches Framework kennt und nutzt, im Hinblick auf Produktivität und Arbeitsgeschwindigkeit vorteilhaft. Gleichzeitig kommen mit diesen Frameworks jedoch auch sehr viele Code-Zeilen, die oftmals gar nicht benötigt werden. Um beispielsweise ein CSS-Grid zu erhalten, muss man nicht zwingend direkt Bootstrap einbinden, das mehrere hundert Kilobyte groß ist.

Bei der Nutzung eines solchen Frameworks, aber auch bei CSS-Resets, sollte man sich also immer vorher Gedanken machen, wie viel man von dem mitgelieferten Code auch wirklich nutzt. Nur wenn das ein Großteil ist, sollte man auch wirklich eine Einbindung vornehmen, da es ansonsten zu einem großen Overhead kommen kann.

3 Kommentare

  1. Schöne Zusammenfassung. Vielen Dank dafür!

    Eine Nachfrage hätte ich: Hast Du Erfahrungswerte dazu, wie stark sich die Beseitigung vermeidbarer „!important“-Anweisungen konkret auf die Performance einer Website auswirken kann? Gibt es da ähnlich frappierende Unterschiede wie in Deinem Beispiel mit den doppelten CSS-Dateien?

    Ich überlege schon seit einer Weile, ob ich nicht einige meiner älteren Sites vor diesem Hintegrund optimieren sollte. Bin mir aber nicht sicher, ob sich der Aufwand lohnt.

    Vielen Dank und schönes Wochenende!
    Jörg

    1. Direkte Erfahrungswerte kann ich nicht aufweisen. Allerdings ist das allein auch schwierig, denn allein durch die Vermeidung von !important sieht das CSS ganz anders aus, wodurch der Parser weniger/mehr „reguläres“ CSS parsen muss. Da ist es eher schwierig, eindeutige und aussagekräftige Vergleiche zu ziehen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.