Startseite / Artikel

Wer sollte das Verhalten der Benutzeroberfläche steuern? Die Wahl zwischen HTML, CSS und JavaScript

Ein Rahmenwerk zur Entscheidung darüber, wann native HTML- und CSS-Funktionen wie Dialoge, Popovers, :has() sowie Container-Queries JavaScript ersetzen sollten und wann nicht.

Verlag: REACTAPP.TOP15 Min. Lesezeit3416 Wörter

Quellen: Not Everything Needs JavaScript Anymore

Frontend-Entwickler greifen instinktiv zu JavaScript. Ein Menü muss geöffnet werden, daher fügen Sie einen Zustand hinzu. Ein Klick muss verarbeitet werden, daher binden Sie einen Listener an. Eine Komponente muss sich ihrer Breite anpassen, daher messen Sie sie; ein Tooltip muss neben einem Button angezeigt werden, daher lesen Sie die Koordinaten; ein Scroll-Effekt ist erforderlich, daher hören Sie auf den Scroll-Vorgang und aktualisieren die Styles. Moderne Frameworks machen all das einfach: React bietet Zustand und Effekte, Angular verfügt über Signale, Vue hat refs und Reaktivität, und Svelte macht Updates außergewöhnlich kompakt. Daran ist nichts falsch, und JavaScript spielt eine große Rolle dafür, dass der Browser zu einer Anwendungsplattform statt zu einem Dokumentanzeiger wurde. Dennoch gewinnen HTML und CSS ständig neue Funktionen, die mit dieser Arbeit überschneiden. Dieser Artikel erklärt, warum das weiterhin geschieht, geht auf die nativen Primitiven ein, die nun gängige Interaktionen abdecken, und gibt Ihnen eine praktische Regel, um zu entscheiden, welche Schicht für einen bestimmten Aufgabenbereich zuständig sein sollte.

eines Verhaltens.

Warum die Plattform weiterhin Aufgaben der Anwendungen übernimmt

Falls JavaScript bereits fast jedes UI-Problem lösen kann, warum bemüht sich die Plattform dann weiterzuentwickeln? Nicht weil JavaScript versagt hat. Es liegt daran, dass der Browser allmählich lernt, Probleme zu bewältigen, die früher jede Anwendung selbst lösen musste. Jedes Mal, wenn das geschieht, erhalten Entwickler eine weitere Option – und die interessantere Frage wird, welche Option langfristig die geringsten Kosten verursacht.

Jede Änderung konkurriert um den nächsten Frame

Es hilft, sich daran zu erinnern, was der Browser nach einer Änderung auf der Seite tut. Ein vereinfachtes Rendering-Pipeline sieht so aus:

JavaScript
    ↓
Style calculation
    ↓
Layout
    ↓
Paint
    ↓
Composite

Echte Engines sind weitaus komplexer. Nicht jede Aktualisierung durchläuft alle Phasen – einige Aufgaben werden außerhalb des Hauptthreads ausgeführt, das Kompositieren kann getrennt stattfinden und bestimmte Animationen vermeiden es, wiederholt Layout- oder Zeichnungsprozesse auszulösen. Dennoch bleibt eine Idee dieses vereinfachten Modells bestehen: der Browser verfügt über einen begrenzten Zeitrahmen, um jedes Frame zu erzeugen. Die Ausführung von Skripten, die Verarbeitung von Eingaben, die Berechnung von Stilen sowie das Rendering müssen sich diesen Zeitrahmen teilen. Je mehr Arbeit hinzugefügt wird, desto länger dauert es, bis der Browser reagieren kann. Um genauer zu erfahren, wie viel Zeit jede Art von Änderung kostet, sehen Sie unsere Aufschlüsselung zu Reflow, Repaint und Composite.

Das lässt sich auf einem Entwicklungsrechner mit schnellem CPU, reichlich Speicher und guter Verbindung leicht übersehen. Führen Sie dieselbe App auf einem älteren Telefon im Energiesparmodus mit mehreren offenen Apps aus – dann werden kleine Verzögerungen sichtbar: Eine Animation lässt ein Frame aus, das Scrollen wirkt träge, eine Schaltfläche reagiert erst viel zu spät, sodass es so wirkt, als wäre sie kaputt.

Leistungsoptimierungen drehen sich nicht nur darum, vorhandenen Code schneller zu machen. Oft ist die beste Optimierung nicht, Arbeit zu erzeugen, die der Browser eigentlich gar nicht von Ihnen verlangt hat.

Es geht um Verantwortung, nicht um HTML gegen JavaScript

Es wäre einfach, die Diskussion auf einen Slogan zu reduzieren:

HTML + CSS = good
JavaScript   = bad

Dieser Slogan ist Unsinn. JavaScript löst Probleme, für die HTML und CSS niemals konzipiert wurden. Die gemeinsame Bearbeitung hängt von der Anwendungslogik ab, und Live-Dashboards erfordern die Synchronisierung der Daten. Ausgefeilte Drag-and-Drop-Funktionen, Domänenvorschriften, interaktive Diagramme, Zustandsmaschinen sowie mehrstufige Workflows werden nicht einfach verschwinden, nur weil ein neuer Selektor verfügbar ist.

Die Fälle, die neu überdacht werden sollten, sind viel kleiner. Angenommen, Sie müssen zusätzliche Informationen anzeigen oder verbergen – ein Skript kann das steuern:

button.addEventListener('click', () => {
  panel.hidden = !panel.hidden;
});

Das ist völlig gültiger Code. HTML hingegen kann ein Offenlegungswerkzeug direkt darstellen. Beachten Sie, dass das fehl am Platz stehende h nach dem Schließtag im Auszug ein Tippfehler ist und entfernt werden sollte:

<details>
  <summary>Advanced settings</summary>
  <p>...</p>
</details>h

Der eigentliche Unterschied liegt nicht in der Anzahl der Zeilen. Es geht vielmehr darum, wer das Verhalten kontrolliert. Bei Verwendung eines Skripts ist die Beziehung folgende:

Application owns the behavior

Mit <details> sieht es so aus:

Browser owns the behavior

Wenn der Browser ein Verhalten steuert, übernimmt er auch die Details, an die man sich sonst erinnern müsste: Tastaturunterstützung, der im erweiterten Zustand an assistive Technologien bereitgestellte Inhalt sowie ein konsistentes Verhalten auf verschiedenen Seiten. Dieser Unterschied ist häufig wichtiger, als es auf den ersten Blick scheint.

Warum HTML bei Abschnitten und Formularen nicht stehen blieb

Im Prinzip hätte HTML schon vor langer Zeit aufzuhören können, sich weiterzuentwickeln. Überschriften, Abschnitte, Formulare, Schaltflächen, Links, Bilder und allgemeine Container reichten aus, um alles zu erstellen, wobei alle komplexeren Aspekte den Skripten überlassen blieben.

Betrachten Sie jedoch, wie diese Welt aussieht. Jede Anwendung erfindet jedes Verhalten von Offenlegungen, Dialogfeldern, Popovers und Formularen neu – jeweils etwas anders. Bibliotheken scheinen diese Muster zu normalisieren, Frameworks umschließen die Bibliotheken, und jeder Besucher lädt eine weitere Implementierung dessen herunter, was der Browser ursprünglich bereits nativ verstehen könnte.

Sobald ein Muster überall zu finden ist, beginnt sich eine eingebaute Primitive selbst zu lohnen. Aus dieser Perspektive entwickelt sich modernes HTML nicht zu einem Anwendungsframework; es kodiert allmählich die Interaktionen, die nahezu jede Anwendung ständig neu aufbaut.

Popovers ohne Anwendungsstate

Nehmen wir ein kleines Einstellungsfenster. Eine Komponentenversion beginnt in der Regel mit einer State-Primitive, wie beispielsweise ein Signal in diesem Beispiel oder sein Äquivalent in einem anderen Framework:

const open = signal(false);

Dann häufen sich die Anforderungen:

button click
    ↓
change state
    ↓
show overlay
    ↓
handle outside click
    ↓
handle Escape
    ↓
manage stacking

Mannchmal benötigt das Produkt tatsächlich all das. Für einfachere Fälle bietet HTML nun die Popover-API an. Ein Button weist mit dem Attribut popover über popovertarget auf ein Element hin, und der Browser kümmert sich um das Ein- und Ausblenden:

<button popovertarget="settings">
  Settings
</button>

<div id="settings" popover>
  ...
</div>

Durch das standardmäßige Verhalten von popover (auto) übernimmt der Browser außerdem das leichte Schließen, indem er den Popover schließt, wenn der Benutzer außerhalb davon klickt oder die Taste Escape drückt, und rendernt ihn in der obersten Schicht – sodass man sich nicht mit z-index-Problemen herumschlagen muss. Ein Verhalten, das früher vollständig im Anwendungscode lag, verfügt nun über einen nativen Ausgangspunkt.

Das bedeutet nicht, dass man niemals ein benutzerdefiniertes Dropdown erstellen sollte. Ein Autocomplete mit Fernsuche, Virtualisierung, Mehrfachauswahl und benutzerdefiniertem Tastaturhandling ist ein völlig anderes Problem. Der Punkt ist lediglich, dass ein einfacher Popover nicht mehr unbedingt als Teil des Anwendungszustands beginnen muss.

Dialoge, die von einem echten Element ausgehen

Modale Fenster sind eine gute Veranschaulichung, denn sie erscheinen unkompliziert, bis man eines richtig erstellt. Die erste Version besteht aus einem einzigen Flag:

open = true;

Dann tauchen die eigentlichen Fragen auf:

  • Was passiert mit dem Inhalt hinter dem Modal-Fenster?
  • Wohin geht der Fokus, wenn es geöffnet wird?
  • Kann die Tastatur es schließen?
  • Was passiert und wohin kehrt der Fokus zurück, wenn es geschlossen wird?
  • Wird es über allem anderen angezeigt, in der obersten Schicht?
  • Wie sollte der Hintergrund aussehen und welche Funktion haben?

Jahrelang begannen viele Implementierungen mit einem generischen <div> und bauten alles von Hand auf. Heute bietet HTML ein spezielles Element dafür:

<dialog>
  ...
</dialog>

Ein praktischer Hinweis ist wichtig zu wissen: Das Modalfensterverhalten entsteht durch das Öffnen des Elements mit showModal(). Dieser Aufruf platziert das Dialogfeld an die Oberste Schicht, deaktiviert den Rest der Seite, ermöglicht das Schließen mit Escape und stellt ein ::backdrop-Pseudo-Element zur Gestaltung bereit. Das Öffnen mit show() oder dem Attribut open erzeugt hingegen ein nicht-modales Dialogfeld.

Das native Element löst nicht alle Anforderungen an Modalelemente, und echte Anwendungen können weiterhin Logik darum hinzufügen. Der Ausgangspunkt ist jedoch anders: Man muss das Konzept eines Dialogs nicht mehr selbst erfinden, denn der Browser weiß bereits, was damit gemeint ist. Das spiegelt ein Muster in allen modernen Plattformen wider: native Primitive entfernen keine Anwendungslogik; sie verringern lediglich den Umfang der Logik, die man selbst verwalten muss.

CSS, das auf den DOM-Zustand reagiert

HTML ist nicht die einzige Schicht, die Aufgaben übernimmt. CSS kann nun Beziehungen ausdrücken, für die früher Skripte erforderlich waren.

Betrachten wir ein Formfeld, dessen Umgebungscontainer gestylt werden soll, wenn die Eingabe ungültig ist. Ein Skript könnte die Eingabe überprüfen und eine Klasse im Elternelement synchron halten. Modernes CSS kann diese Beziehung oft direkt angeben:

.field:has(input:invalid) {
  /* invalid state */
}

:has() ändert, was CSS über das Dokument wissen kann. Es wurde lange Zeit als fehlender Eltern-Selektor gefordert, doch die architektonischen Auswirkungen sind wichtiger als der Name: Einige Synchronisierungsprozesse müssen nicht mehr überhaupt durch JavaScript ablaufen. Der DOM enthält bereits die Informationen, und CSS kann darauf reagieren. Es gibt keine Klasse, die man vergessen könnte zu aktualisieren, und keinen Zuhörer, den man entfernen müsste.

Komponenten, die auf ihren eigenen Bereich reagieren

Responsives Design stützte sich traditionell auf Media Queries:

@media (max-width: 700px) {
  ...
}

Das funktioniert gut, wenn nur der Ansichtsbereich relevant ist. Komponenten kümmern sich jedoch oft um etwas anderes. Stellen Sie sich dieselbe Karte vor, die an zwei Orten dargestellt wird:

Main content
┌───────────────────────────────┐
│            Card               │
└───────────────────────────────┘

Sidebar
┌─────────────┐
│    Card     │
└─────────────┘

Der Ansichtsbereich ist in beiden Fällen identisch; der verfügbare Platz für die Karte hingegen nicht. Wiederverwendbare Komponentensysteme haben dies historisch mit Messungskodern gelöst: Das Element wird beobachtet, seine Breite abgelesen, eine Klasse hinzugefügt und dieser Vorgang immer wiederholt, sobald sich das Layout ändert.

Container-Queries ermöglichen es CSS, die eigentliche Frage zu beantworten – nämlich, wie viel Platz eine Komponente tatsächlich hat. Man markiert einen Vorfahren als Container und definiert Regeln bezüglich seiner Größe:

.card-container {
  container-type: inline-size;
}
@container (width < 400px) {
  .card {
    /* compact layout */
  }
}

Das ist mehr als nur eine neue responsive Syntax; es verschiebt die Verantwortung. Der Browser hat das Layout bereits berechnet, und CSS kann dieses Ergebnis nutzen, ohne dass Anwendungscode es erneut messen muss. Unser Vergleich von Container-Queries und Media-Queries erläutert, wann welche Methode in einem Designsystem zum Einsatz kommt.

Positionierung zeigt, wie sich die Plattform entwickelt

Das Hinzufügen eines Tooltips zu einem Button scheint eine einfache Aufgabe zu sein, bis man es tatsächlich ausprobiert. Wo genau befindet sich der Button? Wie groß ist das Tooltip? Gibt es Platz darunter, oder sollte es nach oben gedreht werden? Was passiert beim Scrollen, bei einer Änderung der Ansichtsgröße oder wenn ein anderer Teil des Layouts den Button verschiebt? Ein einfaches Tooltip verwandelt sich schnell in ein Geometrieproblem.

Deshalb gibt es spezielle Bibliotheken für floating UIs, die ein wirklich schwieriges Problem gut lösen. CSS verfügt mittlerweile über Anchor Positioning, das es ermöglicht, ein Element relativ zu einem anderen Element deklarativ zu positionieren. Zum Zeitpunkt der Erstellung war die Browserunterstützung noch ungleichmäßig, daher sollten Sie aktuelle Kompatibilitätsdaten prüfen und bei der Einführung einenFallback bereithalten.

Was am wichtigsten ist, ist nicht, ob CSS über Nacht jede Positionierungsbibliothek ersetzt. Vielmehr ist es so, dass die Plattform Positionierung als ausreichend verbreitet erkennt, um primitivere Mechanismen zu benötigen. Das ist der übliche Wachstumszyklus einer Plattform: Probleme entstehen in Anwendungen, Muster wiederholen sich, Bibliotheken entstehen und schließlich lernt die Plattform daraus.

Animationen und der Wert deklarativer APIs

Bei Animationen wird die Diskussion über Leistungsdetails komplexer. Eine gängige Vereinfachung besagt, dass CSS-Animationen auf der GPU und JavaScript-Animationen auf dem CPU ablaufen. In Wirklichkeit ist die Situation komplizierter: CSS-Animationen erfordern trotzdem Rechenleistung, JavaScript kann äußerst flüssige Animationen erzeugen, und die Kosten für CSS-Eigenschaften variieren stark. Das Anpassen von Abmessungen kann die Layout-Verarbeitung auslösen, andere Eigenschaften erfordern einen Neumalvorgang, während transform und opacity in der Regel viel freundlicher gegenüber dem Kompositor sind.

Die nützliche Unterscheidung besteht daher nicht zwischen CSS und JavaScript, sondern darin, wie viel Rechenleistung jede Änderung erfordert und wie viel Freiheit man dem Browser lässt, diese Arbeit zu planen.

Scroll-basierte Animationen veranschaulichen diesen Wandel. Ein Scroll-Effekt sah traditionell wie dieser Zyklus aus:

scroll event
    ↓
JavaScript calculation
    ↓
update styles
    ↓
repeat

Einige dieser Beziehungen können nun direkt dem Rendering-Engine mitgeteilt werden. Anstatt dem Browser immer wieder mitzuteilen, einen Wert zu berechnen, anzuwenden und erneut zu wiederholen, beschreibt man hier, dass eine Animation entlang einer Scroll-Zeitlinie voranschreitet. Dies gehört zu einer völlig anderen Kategorie von APIs, bei denen der Browser mehr Kontrolle über den Mechanismus hat und diesen optimieren kann. Wie bei der Positionierung von Ankerpunkten sollte man die Unterstützung in den Zielbrowsern überprüfen.

Das Entfernen von JavaScript entfernt mehr als nur Bytes

Die Reduzierung von JavaScript wird in der Regel in Kilobytes diskutiert, und das ist wichtig, denn jedes Skript muss diesen Prozess durchlaufen, bevor es etwas Nützliches tun kann:

downloaded
    ↓
parsed
    ↓
compiled
    ↓
executed

Doch der Lebenszyklus von Anwendungscode dauert lange Zeit an, nachdem er zum ersten Mal ausgeführt wurde. Verhalten, das in JavaScript implementiert ist, neigt dazu, Folgendes mitzubringen:

state
event listeners
synchronization
cleanup
framework lifecycle
hydration
tests

Stellen Sie sich nun vor, eine native primitive würde es ermöglichen, diese Kombination zu löschen:

state
+
outside-click listener
+
Escape handler
+
DOM synchronization

Der entfernte Code wiegt vielleicht nur ein paar hundert Bytes, doch die architektonische Veränderung ist real. Ihre Anwendung weist dieses Verhalten nicht mehr auf. Es gibt eine State-Variable weniger, die veraltet werden kann, einen Listener weniger, den jemand vergessen könnte zu entfernen, eine DOM-Beziehung weniger, die mit dem State synchron gehalten werden muss, sowie eine Interaktion weniger, die von Grund auf neu erstellt werden muss – oft mit schlechterer Zugänglichkeit als die native Version. Oft liegt der größte Vorteil einer Plattformfunktion nicht in der Geschwindigkeit, sondern in der Verantwortung, die Sie dem Browser zurückgeben.

Die Plattform hat keine endgültige Version

Eine anhaltende Fehlvorstellung erklärt, warum Entwickler diese Änderungen übersehen: Sie sprechen von „HTML5“, als hätte HTML Version fünf erreicht und sei danach größtenteils stehen geblieben. Das ist nicht der Fall. Es wird weder HTML6 entwickelt noch muss man auf HTML7 warten. HTML wird als lebender Standard gepflegt, und die Spezifikation entwickelt sich kontinuierlich weiter.

CSS folgt einem ähnlichen Modell. Viele Entwickler haben „CSS3“ gelernt, doch CSS wird heute nicht mehr als ein großes, versioniertes Paket bereitgestellt. Getrennte Module entwickeln sich in ihrem eigenen Tempo, weshalb es Namen wie diese ohne ein übergeordnetes „CSS6“ gibt:

Selectors Level 4
CSS Color Level 5
CSS Cascade Level

Der letzte Eintrag im Auszug ist abgeschnitten; das Modul für Kaskadierung und Vererbung besitzt außerdem seine eigene Nummerierungsebene. Da die Module unabhängig voneinander sind, kann sich das Grid-System weiterentwickeln, ohne auf Selektoren warten zu müssen, die Farbverarbeitung erfolgt getrennt von Schriftarten, und Container-Queries wurden ohne irgendein fiktives CSS4-Meilenstein veröffentlicht. Im Maßstab des Internets ist eine schrittweise Weiterentwicklung der einzige sinnvolle Ansatz.

Auch JavaScript entwickelt sich weiter

Dasselbe gilt für die Sprache selbst. Die standardisierte Spezifikation ist ECMAScript; JavaScript ist der Name, den Entwickler für die Sprache verwenden, die von Engines gemäß dieser Spezifikation implementiert wird. Deshalb wird der Fortschritt in ECMAScript-Ausgaben beschrieben und nicht in nummerierten „JavaScript“-Versionen, und ständig kommen neue Funktionen hinzu.

Daher ist die Plattform mit dieser Kombination nicht erstarrt:

HTML5
+
CSS3
+
JavaScript

Sie alle drei Schichten entwickeln sich weiter. ECMAScript erhält bessere Primitive, CSS übernimmt mehr Aufgaben im Bereich Layout, Styling und Interaktionsbeziehungen, und HTML bekommt Semantik sowie Verhaltensweisen, die früher von Anwendungen selbst implementiert werden mussten. Die Grenzen zwischen ihnen verschieben sich im Laufe der Zeit, weshalb das Wissen über die Plattform, das man vor Jahren erworben hat, regelmäßig aufgefrischt werden muss.

Plattformwissen bleibt länger aktuell als Framework-Entscheidungen

Diese Funktionen existieren unabhängig von jedem Framework. Eine Container-Abfrage funktioniert genauso, egal ob das Komponente mit Angular, React, Vue, Svelte oder reinem DOM-Code erstellt wurde. :has() kümmert sich nicht darum, wie der DOM erzeugt wurde. Ein natives <dialog> bleibt weiterhin ein Dialog innerhalb eines Frameworks, und ein Popover ist eine Browserfunktion – unabhängig davon, was ihn angezeigt hat.

Dadurch wird das Wissen über Plattformen außergewöhnlich langlebig. Framework-Expertise kann auf Projekte übertragen werden, die dieses Framework verwenden; Plattform-Expertise hingegen kann auf nahezu jedes Webprojekt übertragen werden. Das schmälert die Bedeutung von Frameworks keineswegs, denn sie lösen große architektonische Probleme sowie Probleme im Entwicklererlebnis. Das Verständnis der darunterliegenden Schichten bietet Ihnen eine zusätzliche Option, wenn eine Abstraktion nicht notwendig ist – und zeigt außerdem, was die Abstraktion für Sie leistet. Wenn Sie einen Katalog spezifischer Ersatzlösungen wünschen, geht unsere Übersicht über native HTML-Funktionen, die gängige UI-Bibliotheken ersetzen Funktion für Funktion darauf ein.

Wann nativ die falsche Wahl ist

Bevor man sich hinreißen lässt, eine Warnung: Die Fähigkeit, etwas in HTML oder CSS zu erstellen, bedeutet nicht, dass man es auch tun sollte. Betrachten Sie ein Dropdown, das Folgendes benötigt:

  • asynchrone Suche;
  • Hunderte virtualisierter Optionen;
  • Mehrfachauswahl;
  • fortgeschrittene Tastaturnavigation;
  • individuell angepasste Filterregeln;
  • Optionen, die vom Server geladen werden;
  • Auswahlen, die mit dem Zustand der Anwendung synchron bleiben.

Die Existenz eines einfachen Popovers löst dieses Problem keineswegs, und das Design so zu verzerren, um JavaScript zu vermeiden, würde die Architektur verschlechtern statt verbessern.

Auch CSS verdient denselben Realismus. Es erledigt echte Aufgaben: Layout und Zeichnen sind nicht kostenlos, ein großer DOM bleibt aufwendig in der Verarbeitung, und schlecht gewählte Animationen können weiterhin schlecht performen.

Daher besteht das Ziel nicht darin, HTML und CSS jedes Mal zu verwenden, wenn es technisch möglich ist. Eine weitaus nützlichere Regel lautet:

Nutzen Sie die unterste Schicht, die die tatsächliche Anforderung eindeutig erfüllt.

Mannchmal ist das HTML, manchmal CSS, manchmal JavaScript – und die meisten guten Anwendungen kombinieren alle drei. Eine schnelle Möglichkeit, diese Regel anzuwenden, besteht darin nacheinander zu prüfen: Drückt ein semantisches Element oder Attribut dieses Verhalten aus? Falls nicht, kann CSS es aus dem bereits im DOM vorhandenen Zustand ableiten? Nur wenn auf beide Fragen die Antwort „nein“ lautet oder die Anforderung über das hinausgeht, was die native Version unterstützt, sollte das Verhalten in den Anwendungscode übertragen werden.

Die einfachsten Fälle verdienen die einfachsten Lösungen

Komplexe Funktionen werden weiterhin JavaScript erfordern, und das ist in Ordnung. Doch um diese Funktionen herum enthalten die meisten Anwendungen Hunderte kleiner Interaktionen: erweiterbare Abschnitte, leichte Überlagerungen, Karten, die sich an ihren Platz anpassen, Styling für ungültige Felder, einfache Übergänge sowie das Verfolgen eines Elements durch ein anderes. Immer dann, wenn ein natives Element, ein Attribut oder eine kurze CSS-Regel eine dieser Interaktionen abdeckt, muss Ihre Anwendung sie möglicherweise überhaupt nicht selbst handhaben.

Dass JavaScript dieses Problem lösen kann, ist nichts Besonderes – es kann fast alles lösen. Wertvoll ist vielmehr die Existenz einer weiteren Option.

Aktualisierung des mentalen Modells

Viele Entwickler haben das Web durch eine klare Aufteilung der Arbeit kennengelernt:

HTML → structure
CSS → presentation
JavaScript → behavior

Es handelt sich um ein gutes Modell zum Lernen, doch die heutigen Browser sind leistungsfähiger, als es vermuten lässt. HTML übernimmt nun auch Verhaltensweisen, CSS reagiert auf komplexe Beziehungen im DOM und in der Layout-Struktur, und die Sprache selbst gewinnt ständig neue Funktionen hinzu. Die Plattform änderte sich weiter, selbst wenn die Gewohnheiten unverändert blieben.

Viele dieser Gewohnheiten wurden als Fakten angesehen: Überlagerungen benötigen den Anwendungsstatus, die Positionierung erfordert Skripte, Komponenten können nur auf Breakpoints im Ansichtsfenster reagieren, Scroll-Animationen brauchen Zuhörer-Objekte. All das mag einst zutreffend gewesen sein – doch heute gilt es nicht mehr immer.

Deshalb geht es beim Mithalten bei HTML und CSS nicht darum, clevere Syntaxen zu sammeln. Eine neue Browserfunktion kann Abhängigkeiten, Event-Listener, Zustandsinformationen, ResizeObserver-Objekte oder jeden Code beseitigen, dessen einzige Aufgabe darin bestand, etwas nachzubilden, was der Browser nun nativ versteht.

Kernpunkte

  • Der Browser übernimmt immer wieder Verhaltensweisen, die von Anwendungen erneut implementiert werden; jede solche Übernahme bietet eine kostengünstigere Alternative.
  • Die Frame-Zeit ist ein gemeinsamer Ressourcenpool, weshalb das Vermeiden unnötiger Aufwände oft die effektivste Optimierung darstellt.
  • Denken Sie in Begriffen der Verantwortung: <details>, die Popover-API sowie <dialog> mit showModal() überlassen das Verhalten dem Browser, einschließlich der Zugänglichkeitsdetails, die sonst von Ihnen gepflegt werden müssten.
  • :has() und Container-Queries ermöglichen es CSS, auf den DOM-Zustand sowie die Größe von Komponenten zu reagieren, ohne Synchronisierungs- oder Messcode verwenden zu müssen.
  • Die Positionierung von Ankerpunkten und scrollbasierte Animationen zielen in dieselbe Richtung, doch prüfen Sie vor der Nutzung die aktuelle Browserunterstützung.
  • Natives Verhalten ist nicht automatisch besser: Komplexe Widgets gehören weiterhin zu JavaScript, und auch CSS-Implementierungen haben reale Kosten.
  • Bevor Sie eine weitere Zustandsvariable, Hook-Funktion, Signale, Direktive, Zuhörerfunktion oder Abhängigkeit hinzufügen, prüfen Sie zunächst, ob die Plattform dies bereits kann, und verwenden Sie die unterste Schicht, die die Anforderungen sauber erfüllt.