CSS Kurs Teil 2

Teil 2:
3 Tipps, mit denen ihr euer CSS skalierbar und einfach haltet

Im zweiten Teil unserer Artikelserie CSS-Kurs für Contao beschäftigen wir uns mit der Organisation von CSS-Anweisungen. Mit diesen 3 Tipps, haltet ihr eurer CSS einfach und gut erweiterbar.

Ob und wie gut Code und CSS eines Projektes sind, erfährt man oftmals nicht während der Projektarbeit, sondern erst, wenn man das fertige Projekt wieder anpackt. Sei es, um es zu erweitern, die Darstellung anzupassen oder um Bugs zu beheben.

Für unsere Artikelserie habe ich mir ein paar unserer größeren Projekte der letzten Jahre angesehen. Dabei ist mir aufgefallen, dass vor allen Dingen 3 Fehler dazu führten, dass sich die Projekte später deutlich schwieriger verwalten und erweitern ließen.

Mit diesen 3 Tipps jedoch hätten wir unsere Probleme vermeiden können:

Schiff grau

1. Vermeidet IDs für eure CSS-Anweisungen

IDs haben bei vielen Webentwicklern einen schlechten Ruf: Ihre Anweisungen lassen sich nicht vernünftig wiederverwenden, sie sind schwer zu überschreiben und lassen sich vom Browser langsamer interpretieren als Klassen.

Ab und zu versucht sich mal jemand daran, das Gegenteil zu beweisen, wie zuletzt Stephan Heller in seinem Artikel Don't use IDs in Selectors - Ein Web Mythos.

Aber obwohl der Artikel eigentlich die Nutzung von IDs befürworten sollte, wird an einigen Stellen und spätestens in den Kommentaren deutlich, warum man IDs generell nicht für die Gestaltung einer Website nutzen sollte.

Wir haben die einzelnen Punkte noch mal für euch aufgeschlüsselt:

Schlechte Wiederverwendbarkeit

Bereits im Grundkurs HTML & CSS haben wir irgendwann mal gelernt, dass man IDs nur für Elemente verwenden soll/darf, die auf einer Seite einmalig vorkommen. Damit nehmen wir uns aber auch gleichzeitig auch die Chance, die CSS-Anweisungen – oder zumindest Teil davon – an anderer Stelle wiederzuverwenden.

Gleichzeitig bieten IDs jedoch gegenüber Klassen keinen nennenswerten Vorteil. Oder anders gesagt: Wenn man die Wahl zwischen einer ID und einer Klasse hat, gibt es keinen Grund auf IDs zu setzen.

Spätestens, wenn ihr eure CSS-Anweisungen modular aufbauen wollt (dazu kommen wir noch), sind IDs eine Sackgasse.

Schwer überschreibbar

Da IDs bei der Verarbeitung durch den Browser eine höhere Priorität als Klassen genießen, kann man sich schnell verzetteln, wenn man IDs und Klassen wahllos kombiniert.

Eine Hilfe bietet der CSS Specifity Score, mit dem sich die Wertigkeit von Selektoren berechnen lässt. Euer Ziel sollte dabei sein, einen möglichst geringen Score zu erzielen, denn je niedriger der Score, desto einfacher ist es, die Anweisungen später durch andere Anweisungen zu überschreiben.

Folgende Werte werden der Berechnung dabei zu Grunde gelegt:

  • Inline Styles: 1000
  • IDs: 100
  • Klassen: 10
  • Element: 1

Wenn ich also eine Anweisung mit .nav li {…} deklariere, erhalte ich einen CSS Specifity Score von 11. Diese Anweisung ließe sich relativ leicht überschreiben. Bei einer Navigation mit 2 Ebenen würde es bereits ausreichen, wenn ich die Listenelemente mittels .level_2 li {…}anspreche. Diese Anweisung hat ebenfalls einen Wert von 11 und überschreibt die Original-Anweisungen.

Bei der Verwendung von IDs sieht das schon anders aus. Würde ich meine Navigation mittels ID gestalten, also #nav li {…}, bin ich dazu gezwungen, eine ID auch in den anderen Anweisungen aufzuführen, da meine Anweisungen sonst immer von den Anweisungen mit der ID überschrieben werden würden.

Schlechtere Performance

Im letzten Punkt gebe ich Stephan Heller Recht. Auch wenn IDs vom Browser etwas langsamer interpretiert werden, so ist dieser Unterschied im Gegensatz zu Klassen doch so marginal, dass wir ihn vernachlässigen können. Wenn man von IDs aus Performance-Gründen absieht, sollte man sich auch mal mit der Verarbeitungsgeschwindigkeit von Kindselekor- und Geschwisteselektor-Kombinationen befassen.

Aber die beiden anderen Punkte sind Grund genug, den Einsatz von IDs zu überdenken.

Das Problem: Contao setzt auf IDs

Das Problem ist, selbst wenn wir bei unseren Modulen, Artikeln und Elementen auf Klassen setzen, kommen wir doch nicht ganz ohne IDs aus. Gerade bei wichtigen Layoutbereichen wie Header und Footer, aber auch bei Haupt-, linken und rechten Spalten, setzt Contao standardmäßig auf IDs und nicht auf Klassen.

Deswegen haben wir, obwohl wir es eigentlich besser wussten, in der Vergangenheit zähneknirschend beispielsweise unseren <header> immer über die ID gestylt.

Alternativ hätten wir auch das fe_page Template anpassen können, um den Layoutbereichen eigene Klassen wie .header, .footer etc. hinzufügen können. Da wir allerdings immer darum bemüht sind, unsere Projekte so gut es geht, kompatibel zu zukünftigen Contao-Versionen zu halten, wollten wir nur ungern das Template für ein paar zusätzliche Klassen bearbeiten.

Es gibt aber noch anderen Wege, um beispielsweise den Header oder Footer einer Website zu gestalten, ohne Templates anzupassen:

Elemente direkt ansprechen

Gerade wenn wir von <header> und <footer> sprechen, könnte man doch auch auf die Idee kommen, die Elemente direkt anzusprechen.

Damit geht ihr zwar der ID-Problematik aus dem Weg, allerdings funktioniert das auch nur, solange die Elemente auf der Website einmalig sind. Wenn ihr euch aber die HTML5-Spezifikationen mal genauer anseht, werdet ihr feststellen, dass <header> und <footer> nicht zwangsläufig nur als Layoutbereiche gedacht sind.

Das <header>-Element könnte zum Beispiel auch dafür genutzt werden, um Überschrift und Meta-Informationen wie Datum und Autor einer Nachricht zu gruppieren.

Da nicht auszuschließen ist, dass diese Elemente in Zukunft in Contao auch noch für andere Zwecke genutzt werden, raten wir euch davon ab, die Elemente direkt zu stylen.

IDs mittels Attribut-Selektoren ansprechen

Es gibt aber noch einen kleinen Trick, mit dem ihr IDs in CSS benutzen könnt, ohne die Nachteile einer ID – mittels Attribut-Selektoren.

Wenn ihr also bisher euren Header so angesprochen habt

#header {
    position: fixed;
    top: 0;
}

könnt ihr ihn auch genauso gut mithilfe des Attribut-Selektors gestalten:

[id="header"] {
    position: fixed;
    top: 0;
}

lies: „jedes Element, dass ein Attribut id mit dem Wert header besitzt)

Zugegeben, der Attribut-Selektor ist in seiner Schreibweise etwas unhandlicher als eine ID oder eine Klasse. Er bietet aber gegenüber der ID einen entscheidenen Vorteil: Er wird genauso gewichtet wie eine Klasse (mehr dazu unter CSS Specifity Score) und lässt sich deshalb wesentlich besser durch andere Klassen modifizieren, kombinieren und überschreiben.

Was die Performance betrifft können Attribut-Selektoren zwar nicht ganz so schnell wie Klassen vom Browser interpretiert werden (wir sprechen hier von wenigen zehntel Millisekunden), sie sind allerdings auch nicht langsamer als IDs.

Und auch um die Browser-Unterstützung müsst ihr euch keine Gedanken machen, denn selbst der Internet Explorer 7 unterstützt Attribut-Selektoren.

Wann IDs weiterhin sinnvoll sind

IDs haben in CSS-Anweisungen nichts verloren PUNKT Sie verursachen mehr Probleme, als das sie helfen können.

Dennoch haben Sie weiterhin eine Daseinberechtigung. Ihr könnt IDs zur Ansprache mittels Javascript nutzen oder als Sprungmarken innerhalb einer Seite. Allerdings solltet ihr nicht der Versuchung nachgeben, sie für das Styling per CSS zu nutzen.

ZL;NG

IDs sollten nie für die Gestaltung genutzt werden. Sie haben keine Vorteile, aber jede Menge Nachteile. Wenn ihr Layoutbereiche wie Header und Footer in Contao gestalten wollt, solltet ihr auf Klassen oder den Attribut-Selektor zurückgreifen.

Kompass grau

2. Vermeidet unnötige Verschachtelungen

Wer mit einem Präprozessor wie SASS oder LESS arbeitet, hat die Regel vermutlich schon gehört: Selektoren sollten nicht tiefer als in 3 Ebenen verschachtelt werden. Denn durch das vereinfachte Nesting mithilfe eines Präprozessors lassen sich wahre CSS-Selektor-Monster mit 7 oder mehr Ebenen erschaffen, die man so mit einfachem CSS wahrscheinlich nicht schreiben würde. Aber selbst wenn ihr keinen Präprozessor nutzt, solltet ihr versuchen, nicht mehr als 3 Ebenen zu verschachteln.

Früher war es durchaus üblich, die Gruppierung von Eltern- und Kindelementen durch ihre Verschachtelung zu kennzeichnen. Wenn ihr heute allerdings innerhalb eurer CSS-Anweisungen an eine Stelle wie diese kommt

.teaser { … }
.teaser .image { … }

solltet ihr prüfen, ob ihr die Verschachtelung nicht evtl. umgehen könnt, indem ihr die beiden Klassen unabhängig voneinander schreibt.

.teaser { … }
.teaser-image { … }

Durch den gemeinsamen Wortstamm lässt sich .teaser-image zweifelsohne als Kind von .teaser erkennen, allerdings sparen wir uns an dieser Stelle eine unnötige Verschachtelung.

Verschachtelungen gering halten

Ganz ohne Verschachtelungen geht es aber eben doch nicht. Wenn ich an unser .teaser-image denke, werden vermutlich weitere Anweisungen folgen, z.B.:

.teaser-image { … }
.teaser-image .image_container { … }
.teaser-image img { … }

Aber immerhin bleiben wir bei den empfohlenen 3 Ebenen. Damit bleibt unser CSS übersichtlicher, wir halten den CSS Specifity Score etwas geringer und machen es uns dadurch einfacher, die Anweisungen im späteren Verlauf überschreiben oder wiederverwenden zu können.

Es gibt allerdings auch ein paar Situationen, wo Contao und die empfohlene Selektor-Verschachtelung von maximal 3 Ebenen sich in die Quere kommen.

Mein Lieblingsbeispiel in diesem Zusammenhang ist die Gestaltung einer Navigation: Wir wollen eine Hauptnavigation stylen, die aus Haupt- (.level_1) und Unterpunkten (.level_2) besteht. Um die Hauptpunkte zu gestalten, nutzen wir

.nav > ul > li {
	float: left;
	margin: 0 10px;
}

Um unsere Gestaltung auf .level_1 zu beschränken, nutzen wir die Kind-Selektor-Kombination E > F. Damit haben wir allerdings auch schon unsere 3 Ebenen für die empfohlene Verschachtelungstiefe erreicht.

Wenn wir jetzt noch den Link von .level_1 gestalten wollen würden, wären wir schon bei Ebene 4, mit :hover erreichten wir sogar Ebene 5.

.nav > ul > li > a:hover {
	color: #222;
}

Ich glaube, ich habe mittlerweile alle erdenklichen Kombinationen ausprobiert, um das irgendwie über die bereits vorhandenen Contao-Klassen zu lösen, leider vergeblich (wenn ihr doch eine Lösung kennt, immer her damit). Hier muss man sich einfach überlegen, ob man an dieser Stelle mal eine Ausnahme macht.

Mit zwei kleinen Anpassungen in der .nav_default.html5 könnten wir uns aber einen Großteil der Kindselektoren-Kombination ersparen:

Dafür ergänzen wir jedes Listenelement um eine Klasse, die das Level und den Identifier enthält, zum Beispiel .level_1-item. Nach dem gleichen Prinzip können wir auch dem Anker eine Klasse .level_1-link hinzufügen.

Und schon wären wir wieder im grünen Bereich, denn die Elemente lassen sich nun direkt ansprechen:

.nav .level_1-item { … }
.nav .level_1-link { … } 
.nav .level_1-link:hover { … } 

Wie gesagt, hier müsst ihr selbst entscheiden, ob ihr eine Anpassung am Template vornehmt und zusätzliche Klassen hinzufügt, oder ob ihr die 3 Ebenen-Regel an dieser Stelle brecht. Vielleicht kommt da aber auch noch mal etwas von den Contao-Core-Entwicklern, indem sie den Listenelementen und Links in der Navigation weitere Klassen spendieren.

ZL;NG:

Früher war es üblich, Selektoren zu verschachteln, um ihre Zugehörigkeit zu verdeutlichen. Heute gibt man Klassen den gleichen Wortstamm und versucht Selektoren nicht tiefer als 3 Ebenen zu kombinieren.

Steuerrad grau

3. Schreibt modulares CSS

Der Begriff „Modulares CSS“ ist euch wahrscheinlich schon in irgendeiner Weise über den Weg gelaufen. SMACSS, OOCSS und BEM sind nur 3 von vielen Ansätzen, mit denen sich modulares CSS schreiben lässt. Auch wenn sich die Vorgehens- und Schreibweisen unterscheiden, so verfolgen sie dennoch das gleiche Ziel.

Aber was steckt hinter dem Begriff „modulares CSS“?

Ein Erklärungsversuch

Alle 3 erwähnten Ansätze – SMACSS, OOCSS und BEM – folgen 2 Prinzipien:

  1. Das Layout einer Website wird in kleine, logische Module aufgeteilt. Diese werden unabhängig von ihrer Position im Layout gestylt (siehe auch Funktionellen Klassen aus Teil 1 unseres CSS-Kurses).

  2. Gemeinsamkeiten zwischen einzelnen Modulen sollen erkannt und durch gemeinsame und mit einer gemeinsamen Klasse realisiert werden. Zusätzliche Klassen geben den Modulen dann ihre individuelle Gestaltung.

Ein Beispiel: Angenommen, wir haben auf einer Website 2 Navigationen. Auch wenn die beiden Navigationen sich in vielen Dingen unterscheiden – die eine ist horizontal, die andere vertikal, beide arbeiten mit unterschiedlichen Farben – so haben so dennoch ein paar Gemeinsamkeiten.

Deshalb werden die Gemeinsamkeiten einer übergeordneten Klasse zugewiesen:

.nav ul {
	list-style-type: 0;
	margin: 0;
	padding: 0; 
}

.nav li {
	margin: 0 10px;
}

Der Hauptnavigation gebe wir dann weitere, individuelle Anweisungen durch separate Klassen, zum Beispiel:

.nav-main li {
	float: left;
}

Im Idealfall schafft ihr es sogar, durch die ergänzenden Klassen nur neue Anweisungen hinzuzufügen, statt Bestehende zu überschreiben.

Ziele von modularem CSS

Das Ziel von modularem CSS ist es, eine gut pfleg- und erweiterbare Basis für die Gestaltung von Webprojekten zu schaffen. Durch die Struktur der CSS-Anweisungen lassen sich die Klassen gut miteinander kombinieren und eine Vielzahl von Gestaltungssituationen abdecken. Gleichzeitig folgt die Schreibweise dem DRY-Prinzip.

DRY steht für Don’t Repeat Yourself und bezieht sich auf die Art, wie man CSS schreiben sollte. Anfangs wollte der Erfinder Jeremy Clarke lediglich darauf hinaus, dass man sich wiederholende CSS-Anweisungen gruppieren sollte.

Statt

h1 {
	font-family: Open Sans;
	color: #222;
	margin-bottom: 24px;
	font-size: 48px;
}

h2 {
	font-family: 'Open Sans';
	color: #222;
	margin-bottom: 24px;
	font-size: 36px;
}

h3 {
	font-family: 'Open Sans';
	[…]
}

besser

h1, h2, h3 {
	font-family: Open Sans;
	color: #222;
	margin-bottom: 24px;
}

h1 {
	font-size: 48px;
}

h2 {
	font-size: 36px;
}

h3 {
	[…]
}

Mittlerweile geht man aber noch einen Schritt weiter. Statt lediglich die Klassen mit gleichen CSS-Anweisungen zu gruppieren, versucht man nun für alle Elemente mit gleichen Eigenschaften eine gemeinsame Klasse festzulegen und sie den Modulen zuzuordnen. Dadurch lassen sich, gerade bei umfangreicheren Projekten, weitere Zeilen CSS sparen.

Nachteile von modularem CSS

Natürlich hat modulares CSS auch seine Nachteile. Dadurch, dass das Erscheinungsbild eines Moduls nicht mehr von einer Klasse, sondern mitunter von 2, 3 oder mehr Klassen bestimmt wird, wird es auch im HTML-Code etwas voller. War es früher eher unüblich mehrere Klassen für ein Element zu benutzen und zeugte von schlechtem Stil, sieht es heute genau umgekehrt aus. Lieber ein paar Klassen mehr, dafür aber deutlich weniger CSS schreiben.

Das führt leider in letzter Konsequenz auch dazu, dass die Rückverfolgung von Darstellungsfehlern im Web-Inspector oder Firebug durch das Styling mit mehreren Klassen nicht mehr ganz so leicht ist.

ZL;NG:

Mit modularem CSS lassen sich Webprojekte auf relativ einfache Weise pflegbar und erweiterbar halten. Gerade durch das individuelle Hinzufügen oder Entfernen von Klassen habt ihr so die Möglichkeit eine Vielzahl von Gestaltungssituationen relativ einfach abzudecken.

Unser Fazit:

Um eurer CSS einfach zu halten, solltet ihr immer versuchen, eure Klassen so wenig wie möglich zu verschachteln. Maximal 3 Ebenen sind ein guter Richtwert, der sich in 95% der Fälle auch mit Contao so realisieren lässt – auch ohne Template-Anpassungen. Auf IDs solltet ihr dabei generell verzichten, sie bieten euch keine Vorteile gegenüber Klassen, dafür aber jede Menge Nachteile bei der Wiederverwendbarkeit von Anweisungen und dem Überschreiben von Anweisungen.

Mithilfe von modularem CSS könnt ihr eure CSS-Anweisungen reduzieren und besser für den Projektverlauf und die zukünftige Arbeit am Projekt optimieren. Auch für uns ist die Arbeit mit modularem CSS noch relativ neu und auch wenn sich ein paar unserer Problemen dadurch lösen lassen, können wir noch nicht absehen, ob und wenn ja welche neuen Probleme dadurch unter Umständen entstehen. Fest steht aber, dass neue Technologien wie Web Components ebenfalls nach diesem Prinzip vorgehen, es kann also sicherlich nicht schaden, sich schon jetzt mit der Materie vertraut zu machen.

Kommentare

Kommentar von Hella |

Danke für den Kurs, ich hab bisher schon einiges gelernt und bin an vieles erinnert worden :-)
Ein kleiner Hinweis: auf dem iPhone 4 funktioniert die Navigation nicht.

Antwort von Dennis Erdmann

Moin Hella,

gern geschehen. Nachdem wird die letzten Jahre viel von der Contao Community gelernt haben, dachten wir, es wird Zeit auch mal etwas zurückzugeben. Ich hoffe, dass auch in den nächsten Artikeln etwas für dich dabei ist.

Kommentar von Hans-Jörg |

Super Artikel, aus Erfahrung kann ich vieles bestätigen. Zum Thema Selektor-Verschachtelung: Da verstehe ich deine Schwierigkeiten mit dem Standard-Navigations-Template von Contao nicht. Die Levels sind doch bereits durch Klassen markiert, so dass man sich einige Kind-Selektoren sparen kann. Statt .nav > ul reicht ul.level_1. Statt .nav > ul > > li > ul > li > a:hover schreibt man .level_2 > li > a:hover. Man kommt also gut mit drei (oder maximal vier) Verschachtelungsebenen aus.

Antwort von Dennis Erdmann

Moin Hans-Jörg,

das stimmt, solange du nur eine eine Navigation hast. Wenn du aber noch eine weitere Navigation verwendest, die dann vielleicht auch noch abweichend gestaltet werden soll, ist die direkte Ansprache über beispielsweise .nav auf jeden Fall notwendig.

Das zweite, von Dir erwähnte Beispiel würde ich wohl ebenfalls über .nav .level_2 […] lösen, also vermutlich .nav .level_2 a:hover und trotzdem würde ich die empfohlene Verschachtelungstiefe von 3 überschreiten. Deswegen wäre hier eine Template-Anpassung unter Umständen besser.

Was ist die Summe aus 4 und 6?