Wie CSS gut funktioniert und warum es sinnvoll ist, keine ID’s für Stylesheets zu verwenden.
Viele Webentwickler, die ich während meiner HTML5-Trainings nach ihrer Erfahrung bei Webtechnologien frage, geben bei Stylesheets die schlechteste Einschätzung ab, selbst wenn sie sich bei HTML oder Javascript als erfahren einstufen.
„Warum?“, frage ich dann und ich bekomme häufig die gleiche Antwort. „Ich habe ein Projekt geerbt und muss nun Änderungen umsetzen. Ich verstehe nicht, wo ich das CSS verändern muss. Ich suche oft stundenlang herum, die Zeit habe ich aber nicht! Am Ende schreibe ich ein !important
…“
Die Wartbarkeit der meisten Stylesheets ist miserabel …
Stylesheets erreichen irgendwann im Laufe der Zeit einen Zustand der Unwartbarkeit. Jede weitere Änderung fordert vom Entwickler entweder stundenlanges Forschen in CSS Zeilen oder aber den gekonnten, zunächst zeitsparenden Einsatz eines gepflegten !important
. Damit wird die Unwartbarkeit allerdings endgültig zementiert, denn nach einem !important
lässt sich nichts mehr anfügen.
… und die Ansprüche sind hoch.
Am liebsten soll es ein responsiver Baukasten sein, in dem Layouts mit wenigen Klassen hergestellt werden. Darüberhinaus soll er im Laufe der Monate und Jahre wachsen können. Ach ja, und jeder im Team soll sofort mitarbeiten können.
Ich zeige, wie man mit Selektoren und Gewichtungen eine skalierbare und wartbare CSS-Architektur aufsetzt. Es geht um die grundlegendsten Prinzipien von CSS, um Kaskaden und Gewichtungen.
Das Erste: Elemente auswählen und einstellen
Über die Elemente legt man das allgemeine und grundsätzliche Aussehen des HTML’s im eigenen Projekt fest. So lässt sich bereits die Typografie oder ein Grafikschema herstellen.
body { background-color:#fff; }
table { border: 1px solid black; }
td { padding:2px; }
p, td { font-family:Arial; font-size:1rem; }
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Headline Fusce Fermentum</h1>
<p>Lorem ipsum dolor sit.</p>
<p>Egestas Ligula Aenean Adipiscing.</p>
<table>
<tr>
<td>Bibendum Sollicitudin</td>
<td>Risus Sollicitudin</td>
</tr>
</table>
</body>
</html>
Klassen
Für Textabsätze mit anderen grafischen Eigenschaften gibt es Klassen, ähnlich einer Formatvorlagen aus der Textverarbeitung. Eine Klasse kann beliebige Eigenschaften bekommen und fügt sie bei Bedarf per HTML-Attribut <p class="...">
dazu.
/* Element Selector */
p, td { font-family:Arial; font-size:1rem; }
/* Class Selector */
p.teaser { font-size: 1.2rem }
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Headline Fusce Fermentum</h1>
<p class="teaser">Lorem ipsum dolor sit.</p>
<p>Egestas Ligula Aenean Adipiscing.</p>
<table>
<tr>
<td>Bibendum Sollicitudin</td>
<td>Risus Sollicitudin</td>
</tr>
</table>
</html>
Attribute und Pseudoklassen
Bei anderen Elementen, wie zum Beispiel dem Anchor-Tag <a>
ist es sinnvoll, nach Attributen zu unterscheiden. Links <a href="...">
sind etwas anderes als benannte Anker <a name="...">
, aber beide nutzen dasselbe Tag. Wenn man Links also mit grafischen Attributen ausstatten möchte, kann man das [href]
-Attribut ansprechen.
<a name="top"></a>
<a href="#top">jump to top</a>
a[href] { color: blue; }
Attributselektoren ermöglichen einen (immer noch) allgemeinen Zugriff auf HTML Elemente ohne mit Klassen arbeiten zu müssen.
Dasselbe gilt auch für Zustandsattribute wie a[href]:visited
, a[href]:active
, a[href]:hover
und alle andere Variationen von sogenannten Pseudoklassen. Hier wird ein Ankerelement in seinen verschiedenen Zustände beschrieben.
a[href]:visited { color: purple; }
a[href]:active { color: red; }
a[href]:hover { color: blue; }
ID Selektoren
Der ID Selektor #my-id {}
referenziert ein einzelnes Element anhand seiner Id.
So kann ein Element und seine Nachfahrenelemente anhand eines eindeutigen Wurzelelements angesprochen werden. Das ist kurz und hilft im Beispiel bei der Unterscheidung verschiedener Formulare,
<form id="form-login">
...
<button type="submit">enter</button>
</form>
#form-login {
margin: 0.5rem;
}
#form-login button[type=submit] {
...
}
Das letzte gewinnt, die Kaskade bestimmt die Gültigkeit, oder?
Nun heisst es Cascading Style Sheets. Deklarationen fallen in Stufen nach unten, um, dort angekommen, einem DOM Element sein Aussehen mitzuteilen. Aber nicht alle Regeln, die am Beginn der Treppe starten, kommen unten an. Manche werden unterwegs durch andere überschrieben
Kaskaden werden schnell komplex
Kaskaden entstehen aus unterschiedlichen Gründen und sie können mit unterschiedlichen Zielsetzungen genutzt werden.
- Innerhalb einer einzigen Datei folgen Deklarationen aufeinander: Spezifikation von Zuständen und Variationen
- Mehrere Stylesheet Dateien bilden eine kaskadierende Folge: Aufteilung nach Aufgabe oder Kategorie.
Eine Syntax, drei Möglichkeiten
Eine CSS-Deklaration kann sich bei gleicher Syntax auf drei Arten auswirken und das kann verwirrend werden:
p, td { font-family: Arial; }
p { font-size: 1rem; }
td { font-size: 0.95rem; }
p.teaser { font-size: 1.2rem; }
- Definition: Für
<p>
und<td>
wird zunächst eine Schriftart festgelegt. - Ergänzen: Die Schriftgröße wird für beide Element einzeln und verschieden ergänzt.
- Überschreiben: Der Absatz für den Teaser bekommt eine größere Schrift und überschreibt deshalb die
<p>
-Schriftgröße.
Tatsächlich ist die Kaskade hier überhaupt nicht. Die Styles funktionieren auch in einer anderen Reihenfolge.
p.teaser { font-size: 1.2rem; }
p { font-size: 1rem; }
td { font-size: 0.95rem; }
p, td { font-family: Arial; }
Nach der Kaskadenregel gewinnt das zuletzt Genannte und beansprucht die Schriftgröße für sich. In der praktischen Prüfung merken wir aber, dass die Klasse p.teaser
ausgeführt wird und nicht der einfache p
Elemente-Selektor, obwohl er später kommt.
Warum wiegt eine Klasse schwerer als ein Element?
Klassen müssen Elemente überwiegen. Sie spezifizieren besondere Fälle für allgemeine Elemente. Dasselbe gilt für ID-Selektoren, die, weil sie an einzeln genannte Elemente gerichtet sind, noch mehr Gewicht brauchen, als Klassen oder eben Elemente.
Wie das funktioniert, kann beim W3C nachgelesen werden: Calculating a Selectors Specifity, aber hier die Kurzform:
Elemente wiegen 1
, Klassen, Pseudoklassen und Attributselektoren wiegen 10
, ID-Selektoren wiegen 100
. Ausserdem addieren sie sich. Im vorausgegangenen Beispiel finden wir also folgende Gewichtungen:
p { font-size: 1rem; } /* specifity = 1 */
td { font-size: 0.95rem; } /* specifity = 1 */
p, td { font-family: Arial; } /* specifity = 1 */
p.teaser { font-size: 1.2rem; } /* specifity = 11 */
#form-login { margin: 0.5rem; } /* specifity = 100 */
#form-login button[type=submit] { ... } /* specifity = 111 */
Warum sind ID Selektoren keine gute Idee?
Damit kommen wir zum springenden Punkt. Für einen ID-Selektor gilt im Grunde keine Kaskade mehr E er ist mit einer Gewichtung von mindestens 100
so schwer, dass er sich nur noch mit wenigen weiteren Selektoren überschreiben lässt. Nur noch die !important
Ergänzung oder ein inline-style
-Attribut haben eine höhere Gewichtung. .
Ein inline-style
Attribut hat eine Gewichtung von 1000
und eine !important
Klausel hat effektiv eine Gewichtung von 10000
.
#form-login button[type=submit] { color: lightsteelblue; } /* specifity = 111 */
button[type=submit] { color: steelblue; } /* specifity = 11 */
button[type=submit] { color: steelblue !important; } /* specifity = 10011 */
<!-- specifity = 1000 -->
<button type="submit" style="color: steelblue;">enter</button>
Nach mir die Sintflut!
Im Arbeitsalltag fordern bereits existierende ID Selektoren die Verwendung von !important!
heraus. Danach ist dann aber ein weiteres Refactoring so gut wie ausgeschlossen. Jeder, der mit über die Jahre gewachsenen Webtemplates arbeitet, kennt das Problem.
Hört auf, ID Selektoren für Stylesheet zu verwenden!
Kaskade und Gewichtungen lassen sich auch in komplexeren Architekturen völlig problemlos vereinen, solange man nur ID-Selektoren weglässt! Dann ist ein Refactoring völlig problemlos. Und das !important
ist auf einmal eine sinnvolle Ergänzung, denn sie sichert ab, dass sich verändernde Zustände auch zuverlässig angezeigt werden.