Leser fragen und der Erklärbär antwortet nun schon zum dreizehnten Male, heute zu den Themen HTML5-Semantik, DOM und CSS-Selektoren. Falls ihr hierzu oder zu anderen Browser-Themen noch mehr Fragen auf Lager habt, dann schreibt mir eine E-Mail oder gebt die Frage per Twitter ab.

Was passt in Headline-Elemente?

Ist es in HTML5 erlaubt, <p>-Elemente in den Headline-Elementen <h1> bis <h6> zu benutzen?

Nein, in Überschriften dürfen laut Spezifikationen nur Elemente aus der Kategorie phrasing content vorkommen. Das in etwa die Elemente, die man früher unter inline elements subsummiert hätte und so sind <p>-Elemente nicht mit von der Partie. Tipps für kompliziertere Überschrift-Strukturen hat dieser Artikel auf Lager. Am sinnvollsten ist es vermutlich, Überschrift und Absatz in einem gemeinsamen <header>-Element überzubringen.

Laut offizieller Empfehlung für komplexere Überschriften sollte man einfach ein Überschriften-Element zusammen mit z.B. <p>-Elementen in ein gemeinsames <header>-Element verfachten. Möglichweise wird es auch eines Tages ein Element der Marke <subhead> geben; erste Überlegungen hierzu existieren, aber definitiv ist hier noch gar nichts.

Herausfinden welches Element in welchem Element vorkommen darf ist im Übrigen ein Kinderspiel: einfach in den Spezifikationen das Element heraussuchen (hier die Headline-Elemente) und in der grünen (beim W3C blauen) Infobox die Punkte Content model und Contexts in which this element can be used ansehen; der erste verrät, was in diesem Element vorkommen darf, der zweite in welchen anderen Elementen dieses Element stehen kann.

Was ist der beste Ersatz für <main>?

Wenn ich <main> nicht benutzen kann, weil ich alte Browser unterstützen muss, wäre dann ein <section role="main"> oder ein <div role="main"> der richtigere Ersatz?

Ich halte es für etwas schwierig, hier das eine für falscher oder für richtiger zu erklären. Grundsätzlich gilt, dass man immer das passende Element verwenden sollte und nichts anderes. Die Spezifikationen sagen ganz eindeutig: Authors must not use elements, attributes, or attribute values for purposes other than their appropriate intended semantic purpose. So gesehen wären <div role="main"> und <section role="main"> beide nicht richtig, denn es gibt ja <main>. Daher mein Tipp: alten Browsern mittels html5shiv das neue Element einfach beibringen!

Wenn wir uns dann dazu entschließen (aus welchen Gründen auch immer) es falsch zu machen, sehe ich auch keinen so großen Unterschied zwischen <div> und <section> ausmachen. Für das <div> spricht, dass es genau wie <main> keinen Eintrag in der Outline erzeugt, so dass das vermutlich die bessere Wahl wäre. Aber man muss auch ganz ehrlich sagen, dass wir uns, wenn wir mit der Outline argumentieren, schon ganz weit draußen in der Theoriewüste bewegen. Für alles was relevant ist (SEO, Lesbarkeit des Quelltexts, Barrierefreiheit) ist es wirklich ziemlich egal, solange das role-Attribut da ist.

Es gilt wie immer die Faustregel für semantisches HTML: wenn man die Wahl zwischen A und B hat und nach 5 Minuten Nachdenken und Recherche nicht zweifelsfrei klar ist, was richtiger ist, ist es vermutlich ziemlich egal.

DOM-Änderungs-Events

Wie bekommt man mit, wenn sich ein Element im DOM ändert, es zum Beispiel sichtbar gemacht wird?

Für dieses Problem gibt es mehrere Lösungsansätze – die einen funktionieren in vielen Browsern und sind eher schlecht, die anderen sind eher gut, funktionieren bloß fast nirgends. Die beste Lösung ist der Mutation Observer (Specs, MDN), der einen Überwachungsprozess für einen DOM-Knoten darstellt. Mit new MutationObserver(callback) wird ein solcher Überwachungsprozess erstellt und mit der Methode observe(target, options) auf ein Ziel-Element angesetzt:

var target = document.querySelector('p');

// Neue MutationObserver-Instanz
// Das Callback-Argument "changes" ist ein Array von Änderungen,
// self ist der MutationObserver selbst
var observer = new MutationObserver(function(changes, self){
  console.log('Änderung registriert', changes);
  self.disconnect(); // Überwachung beenden
});

// Startet die Überwachung von "target" mit den im Objekt
// gelisteten Optionen. Hier interessieren wir uns nur für
// Änderungen des Class-Attributs
observer.observe(target, {
  attributes: true,
  attributeFilter: ['class']
});

MutationObserver können Inhalt, Attribute und Subtree eines Elements überwachen. Was genau überwacht werden soll, wird im zweiten Parameter der observe()-Methode angegeben. Wenn Änderungen passieren, feuert der beim erstellen des Observers angelegte Callback, wo die einzelnen Änderungen dem Array im ersten Parameter zu entnehmen sind. Hier ist das Ganze in Aktion

Was Mutation Observer nicht können, ist überall funktionieren – im IE gibt es sie z.B. erst ab Version 11 (siehe Can I use). Allerdings gibt es in vielen Browsern noch Fragmente der Mutation Events (MDN), die auf anderem Wege auch das Ziel der DOM-Überwachung im Visier hatten. Aus unter anderem Gründen der Performanceoptimierung (Observer sind viel schneller) wurden die Mutation Events allerdings relativ schnell offiziell aufgegeben, sind aber immerhin bis runter zum IE 9 noch vorhanden. Was tun, wenn man zwei verschiedene APIs hat die das Gleiche machen? Man nutzt einen Polyfill, der alle Browser und APIs vereinheitlicht, sofern sie denn überhaupt eine Möglichkeit zur DOM-Überwachung bieten. In IE 8 oder noch älteren Semestern ist DOM-Überwachung also nicht möglich, in allen anderen kriegt man das Problem zumindest auf eine einheitliche API zurechtgebogen.

Rückwärts matchende CSS-Selektoren?

Gibt es eine Möglichkeit per CSS alle Geschwisterelemente eines Elements anzusprechen? Mit dem General Sibling Combinator bekomme ich nur die nachfolgenden Geschwisterelemente, aber nicht die vorhergehenden.

Aktuell gibt es keine Möglichkeit, in CSS irgendwie rückwärts zu matchen, d.h. Elternelemente oder vorhergehende Geschwisterelemente anzusprechen. In Selectors Level 4 wird z.Z. ein für Parent-Selektoren benutzbares Feature beschrieben (E! > F spricht E an, wenn es das Elternelement von F ist), aber dieses Feature gibt es aktuell in keinem Browser und es ist auch alles andere sicher, dass sich das je ändern wird.

Das Grundproblem ist, dass CSS-Selektoren eigentlich eine Einbahnstraße sind. Browser lesen aus Performancegründen Selektoren wie .foo + section a immer von rechts nach links. In der Regel gibt es pro zu stylendem Element mehr CSS-Regeln nebst Selektoren, die das Element nicht betreffen als solche, die relevant sind. Um möglichst zügig entscheiden zu können, welche Styles der Browser auf ein Element anwenden muss, arbeitet er nach einem von rechts nach links laufendem Ausschlussverfahren.

Im Falle von .foo + section a beginnt er mit allen <a>-Elementen (was schon mal 95% aller übrigen Kandidaten aus der Suche eliminiert) und sortiert dann alle aus, die kein <section>-Element als Vorfahren haben. Am Ende fliegen dann <a>-Elemente, deren <section>-Vorfahren nicht direkt auf Elemente mit der Klasse foo vor sich haben aus der Auswahl und es bleiben nur die durch den Selektor beschriebenen Elemente übrig. Auf diese Weise wird der Löwenanteil der Element-Kandidaten im ersten Schritt (ist das Ziel-Element ein <a>?) aussortiert und der Rest-Selektor braucht gar nicht ausgewertet zu werden. Von links ausgewertete Selektoren würden es erfordern, dass große Teile des DOM gründlich nach möglichen Kandidaten durchsucht werden, also z.B. auch <section>-Elemente, die gar keine <a>-Elemente enthalten.

Bei einem Selektor für alle Geschwisterelemente ist das Performance-Problem vielleicht nicht ganz so groß, aber trotzdem müsste man der CSS-Engine beibringen, auch folgende Elemente in das Ausschlussverfahren mit einzubeziehen. Und das ist ein Feature, das es aktuell einfach aufgrund der fundamentalen Konzeption des Selektor-Parsers (Stichwort Bottom-Up-Parser) nicht gibt.

Dinge wie ein Parent-, oder Geschwister-Selektor würden den Matching-Algorithmus also um einiges komplizierter und vor allem langsamer machen. Der durch die Einführung eines Parent-Selektors entstehende Geschwindigekeitsnachteil wäre so groß, dass die Spezifikationen von Selectors Level 4 sogar unterschiedliche Performance-Profile definieren, von denen eins besonders schnell ist, aber eben Features wie den Parent-Selektor nicht unterstützt.

Müsste ich wetten, würde ich nicht darauf setzen, dass der Parent-Selektor oder ein Geschwisterelemente-Kombinator zeitnah in unseren Browsern aufschlägt.

Weitere Fragen?

Auch eure Fragen zu HTML5, JavaScript und anderen Webtechnologien beantworte ich gerne! Einfach eine E-Mail schreiben oder Twitter bemühen und ein bisschen Geduld mit der Antwort haben. Ansonsten kann man mich natürlich auch als Erklärbär zu sich kommen lassen.