JavaScript und CSS

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Dieses Tutorial zeigt, wie Sie mit JavaScript die Darstellung des Dokuments dynamisch ändern können, während es im Browser angezeigt wird: Beispielsweise können Sie als Reaktion auf eine Benutzereingabe gewisse Elemente ein- oder ausblenden. Es sind aber auch – mit entsprechendem Aufwand – visuelle Effekte und komplexe Animationen möglich.

JavaScript besitzt keine eigenen Techniken zur Gestaltung einer Webseite, sondern stellt lediglich eine Schnittstelle zur Formatierungssprache Cascading Stylesheets (CSS) bereit. Mittels JavaScript können Sie also sämtliche Formatierungen vornehmen, die CSS möglich macht.

Bevor Sie die verschiedenen Möglichkeiten kennenlernen, wie Sie CSS-Formatierungen durch JavaScript verändern können, sollten Sie sich die Konzepte des Unobtrusive JavaScript in Erinnerung rufen.

  • HTML ist für die Strukturierung der Inhalte zuständig. Idealerweise bleiben die HTML-Strukturen übersichtlich, IDs und Klassen sind sparsam gesetzt und aussagekräftig, sodass eindeutige Angriffspunkte für CSS-Regeln existieren.
  • CSS-Formatierungen werden in Stylesheets ausgelagert und sprechen gezielt Elemente im Dokument an.
  • Wenn nun die dritte Technik – JavaScript – hinzutritt, sollten Sie dieses Modell konsequent fortführen.
Empfehlung: Orientieren Sie sich an folgenden Faustregeln:
  • Definieren Sie die Formatierungsregeln im zentralen Stylesheet, nicht im JavaScript. Trennen Sie CSS-Anweisungen vom JavaScript-Code.
  • Sorgen Sie im JavaScript dafür, dass diese Formatierungsregeln angewendet werden – beispielsweise indem Sie ein Element dynamisch einer Klasse hinzufügen. Durch diese Änderung der Klassenzugehörigkeit kann eine Regel im Stylesheet greifen, deren Selektor die soeben gesetzte Klasse enthält.
  • Sie können nicht nur ausgelagerte Stylesheet-Regeln auf ein Element anwenden, sondern auch direkt bestimmte CSS-Eigenschaften einzelner Elemente ändern. Dies entspricht dem style-Attribut in HTML. Diese Vermischung von HTML und CSS bzw. JavaScript und CSS sollten Sie möglichst vermeiden. Diese Direktformatierung ergibt nur in Sonderfällen Sinn, deshalb sollten Sie sich zunächst mit der besagten Arbeitsweise vertraut machen.

Stylesheet-Regeln auf ein Element anwenden

Eine sehr effektive und aus Programmierersicht relativ simple Vorgehensweise ist das Setzen bzw. Entfernen einer Klasse, der ein Element angehört. Wenn man einem Element eine bestimmte Klasse zuweist, die in den Darstellungsregeln so verwendet wird, dass dieses Element anders als ohne diese Klasse aussieht, dann steuert man mit JavaScript indirekt, und damit der Trennung der Schichten von Markup, Layout und Behaviour folgend, das Aussehen dieses Elements – und seiner Nachfahren oder Geschwister.

Der Clou dieser Vorgehensweise ist, dass Sie mit dem Setzen der Klasse an einem Element nur eine minimale Änderung am DOM vornehmen. Diese Änderung führt dazu, dass eine Regel aus dem Stylesheet plötzlich auf bestimmte Elemente greift – der Browser wendet daraufhin automatisch die definierten Formatierungen an.

className und classList

Um nun einem Element eine Klasse zuzuweisen oder wieder zu entfernen gibt es – auch wieder aus historischen Gründen – zweierlei Möglichkeiten:

  1. className – einfach zu nutzen bei nur einer Klasse
  2. classList – bequem zu nutzen, vor allem bei mehrfacher Klassenzuweisung

Die Eigenschaft className entspricht in etwa dem class-Attribut im HTML-Quelltext. Dieses Attribut kann, durch Leerzeichen getrennt, verschiedene Klassen enthalten. Das Manipulieren der className-Eigenschaft ist aber genau dann mühsam, wenn man mit mehreren Klassen hantieren muss.

Daher ist es nun sinnvoll, auf die Verwendung von className zugunsten von classList zu verzichten.

einfache Klassenzuweisung in JavaScript
// body die Klasse "js-enabled" geben
document.body.className = "js-enabled"; // entspricht <body class="js-enabled">
document.body.classList.add("js-enabled");

// body nun die Klasse "js-enabled" wieder wegnehmen:
document.body.className = ""; // entspricht <body class="">
document.body.classList.remove("js-enabled");
Das body-Element wird der Klasse js-enabled zugeordnet. Beispielhaft geschieht dies doppelt, sowohl mit className als auch mit classList. Die Methoden add und remove prüfen, ob eine Klasse dieses Namens bereits vorhanden ist. Daher wird mit classList.add die Klasse js-enabled nicht doppelt vergeben.

Die Vorteile von classList werden ganz schnell deutlich, wenn mit mehreren Klassen hantiert werden muss:

mehrfache Klassenzuweisung in JavaScript
// body die Klassen "js-enabled" und "unconfirmed" geben
document.body.className = "js-enabled unconfirmed"; // entspricht <body class="js-enabled unconfirmed">

document.body.classList.add("js-enabled", "unconfirmed");

// body nur die Klasse "unconfirmed" wieder wegnehmen:
document.body.className = document.body.className.replace(/\b?unconfirmed/, "");
document.body.classList.remove("unconfirmed");
Um mit className die Klasse unconfirmed zu entfernen, muss eine Stringoperation durchgeführt werden, welche hier mit der String-Methode replace unter Zuhilfenahme eines regulären Ausdrucks geschieht. Mit den Methoden von classList ist diese „Handarbeit“ nicht mehr notwendig.
Empfehlung: Wenn die zu ändernde Klasse bei vielen Elementen hinterlegt ist und Sie nicht alle Elemente aufsuchen wollen, können Sie dem body eine "Schalterklasse" wie mode-1 und mode-2 geben und im Stylesheet Regeln pro Mode in dieser Art festlegen:
.mode-1 .klasse1 {
   ... einstellungen
}
.mode-2 .klasse1 {
   ... einstellungen
}
Auf diese Weise ändern alle Elemente mit Klasse "klasse1" ihre Styles, sobald am body die mode-Klasse geändert wird.[1]

Zusammenspiel von CSS-Regeln und Klassenzuweisung

Nehmen wir einmal an, wir wollten einen Lückentext gestalten, bei dem die Lücken auf Knopfdruck sichtbar werden sollen. Dazu bräuchten wir ein passendes Element (z. B. span), welches die Wörter enthält, die später zu Lücken werden sollen. Außerdem bräuchten wir eine Darstellungsregel, die den Inhalt unsichtbar macht, wohl aber die Lücken als solche kennzeichnet. Zu guter Letzt bräuchte es eine Klasse, die dafür sorgt, dass die ausgeblendeten Wörter wieder dargestellt werden.

Betrachten wir zuerst den CSS-Teil:

Darstellungsregeln für Lückentext
span {
  visibility: hidden;
}

.spoil span {
  visibility: visible;
}
Die CSS-Eigenschaft visibility macht die Inhalte von span-Elementen unsichtbar. Diese Unsichtbarkeit wird bei den span-Elementen wieder aufgehoben, die sich in einem Element befinden, welches entweder selbst die Klasse spoil hat, oder ein Nachfahre eines solchen Elements ist.

Der JavaScript-Teil tut im Wesentlichen nichts anderes, als dem body-Element die Klasse spoil hinzuzufügen, oder zu entfernen:

JavaScript für Lückentext
(function () {
  document.addEventListener("DOMContentLoaded", function () {
    var spoil = document.getElementById("spoil"),
        className = "spoil";

    if (spoil) {
      spoil.addEventListener("click", function () {
        document.body.classList.toggle(className);
      });
    }
  });
}());
Das Script selbst sitzt in einer sofort ausgeführten Funktion, die selbst eine (anonyme) Funktion definiert, welche genau dann ausgeführt werden soll, wenn das HTML-Dokument vollständig geladen wurde. In dieser anonymen Funktion wird nach einem Element mit der ID spoil gesucht, einem button zum Hin- und Herschalten. Die Funktion, die beim Anklicken des Buttons aktiv wird, fügt entweder die Klasse spoil dem body hinzu oder entfernt sie wieder, wofür classList die Methode toggle kennt.

Nun haben wir die wesentlichen Bausteine für den Lückentext zusammen. Mit CSS sorgen wir dafür, dass die Inhalte ausgeblendet und somit Lücken geschaffen werden. Mit JavaScript schalten wir zwischen Rätsel- und Lösungsansicht hin und her. Hier nun eine fertige Rätselseite:

Rätselseite mit Lückentext ansehen …
<button data-on="einblenden." data-off="ausblenden." id="spoil"></button>
button {
  display: block;
  margin: 0 auto;
}

button::after {
  content: attr(data-on);
}

.spoil button::after {
  content: attr(data-off);
}
Damit die Beschriftung des Klickbuttons sich passend mitändert, wurden die notwendigen Beschriftungen als data-Attribute notiert. In HTML5 können Sie jederzeit solche Attribute notieren, um sie z. B. als CSS-generierten Inhalt zu nutzen.

Fortschrittsanzeige bei umfangreicheren Formularen

Über dieses Modell können Sie auch komplexere Aufgabenstellungen lösen, denn Ihnen stehen alle Möglichkeiten der CSS-Selektoren zur Verfügung. Beispielsweise können Sie mittels Nachfahrenselektoren Elemente formatieren, die unterhalb des mit der Klasse markierten Elements liegen. So können Sie durch die Änderung der Klasse gleich mehrere enthaltene, im DOM-Baum unterhalb liegende Elemente formatieren, ohne diese einzeln anzusprechen.

komplexere Darstellungswechsel mittels dynamisch vergebener Klassen ansehen …
<form>
  ...
  <p><button>Daten versenden</button></p>
  <p class="notice">...</p>
</form>
form {
  background: white;
  border: thin solid #f00;
  border-radius: .5em;
  box-shadow: 0 0 10px #f00 inset;
  padding: .5em 1em;
}

form button {
  color: red;
  text-decoration: line-through;
}

form.percentage-100 button {
  color: inherit;
  text-decoration: inherit;
}

.percentage-20 {
  border-color: #f60;
  box-shadow: 0 0 .6em #f60 inset;
}

.percentage-100 {
  border-color: #0f0;
  box-shadow: 0 0 .6em #0f0 inset;
}

.notice {
  border-color: #f00;
  border-radius: .5em;
  box-shadow: 0 0 1.2em #f00 inset;
  padding: 1em 2em;
}

.percentage-20 .notice {
  border-color: #f60;
  box-shadow: 0 0 1.2em #f60 inset;
  padding: 1em 2em;
}

.percentage-100 .notice {
  border-color: #0f0;
  box-shadow: 0 0 1.2em #0f0 inset;
  padding: 1em 2em;
}

Das Beispiel zeigt verschiedene Klassen, die verschiedene Prozentstufen repräsentieren (hier nur eine Auswahl aus .percentage-10 bis .percentage-100). Die Darstellungsregeln für das Formular und den .notice-Textabsatz gehen von einer Prozentstufe null aus, die grundsätzlich angewendet werden. Wenn nun JavaScript bei der Prüfung feststellt, dass mehr als 0% der Angaben gemacht wurden, weist es dynamisch dem form-Element die passende Klasse zu, was sich unmittelbar auf die Farbgebung sowohl des Formulars selbst, als auch dem darin enthaltenen .notice-Textabsatzes, auswirkt, da diese Klassen Teile der bisher gültigen Darstellungsregeln modifizieren.

Zusätzlich zeigt ein progress-Element den Fortschritt beim Ausfüllen als Balken an.

Direktformatierung über das style-Objekt

Inline-Styles in HTML

Um direkt einzelne HTML-Elemente mit CSS zu formatieren, existiert das style-Attribut, welches eine Liste von Eigenschafts-Wert-Zuweisungen enthält.

Ein HTML-Beispiel:

Beispiel
<p style="color: red; background-color: yellow; font-weight: bold;">Fehler</p>
Beachten Sie: Gegenüber dem Einsatz von zentralen Formaten in Stylesheets sind diese sogenannten Inline-Styles (eingebettete Formatierungen) ineffektiv und führen zu einer Vermischung von HTML und CSS, die die Wartbarkeit des Dokuments verschlechtert. Sie sollten Sie daher nur in Ausnahmefällen einsetzen, auf die wir später noch zu sprechen kommen.

Das style-Objekt als Schnittstelle zu Inline-Styles

JavaScript bietet eine Schnittstelle zu diesem style-Attribut: Das style-Objekt bei jedem Elementobjekt. Das style-Objekt hat für jede mögliche CSS-Eigenschaft eine entsprechende les- und schreibbare Objekteigenschaft. Zu der CSS-Eigenschaft color existiert also eine Objekteigenschaft element.style.color vom Type String.

Folgendes Beispiel veranschaulicht das Setzen der Hintergrundfarbe eines Elements auf rot:

Beispiel
document.getElementById('beispielID').style.backgroundColor = 'red';

Als Werte müssen Sie stets Strings angeben genau in der Form, wie sie in CSS spezifiziert sind. Das gilt auch für Zahlenwerte, die eine Einheit erfordern:

Beispiel
element.style.marginTop = 15; // Falsch!
element.style.marginTop = '15px'; // Richtig
Beachten Sie: CamelCase statt Bindestriche!
CSS-Eigenschaftsnamen mit Bindestrichen, wie z. B. background-color, können nicht unverändert als JavaScript-Eigenschaftsnamen übernommen werden. Deshalb werden sie im sogenannten CamelCase notiert: Der Bindestrich fällt weg, dafür wird der darauf folgende Buchstabe zu einem Großbuchstaben. Aus background-color wird also backgroundColor, aus border-left-width wird borderLeftWidth und so weiter. Die Großbuchstaben in der Wortmitte werden mit den Höckern eines Kamels verglichen.
Beachten Sie: Abweichungen vom besagten Schema:
Wenn Sie die CSS-Eigenschaft float mit JavaScript ändern wolllen, müssen Sie den Begriff cssFloat verwenden, da float ein Reserviertes Wort ist.

Sinnvoller Einsatz des style-Objektes

Das Setzen von CSS-Formatierungen direkt über das style-Objekt ist (oberflächlich betrachtet) sehr einfach. Dennoch sollten Sie diese Präsentationsregeln nicht im JavaScript, sondern z. B. in einer Klasse im Stylesheet unterbringen. Nur in manchen Fällen ist die Verwendung von Inline-Styles notwendig: Wenn der Eigenschaftswert nicht fest steht, sondern erst im JavaScript berechnet wird. Das ist der Fall z. B. bei Animationen oder bei einer Positionierung abhängig von der Mauszeiger-Position wie beim Drag-and-Drop.

custom properties

In HTML5 ist es möglich, im Stylesheet CSS-Variablen zu deklarieren, die dann in mehreren Regelsätzen aufgerufen werden können.

Setzen von CSS-Variablen mit JavaScript ansehen …
document.addEventListener('DOMContentLoaded', function () {
	  
  const svg = document.querySelector('svg'),
        currentTime = new Date();

  svg.style.setProperty('--start-seconds', currentTime.getSeconds());
  svg.style.setProperty('--start-minutes', currentTime.getMinutes());
  svg.style.setProperty('--start-hours', currentTime.getHours() % 12);
});

Das Script enthält einen Eventlistener, der nach dem Laden der Webseite mit new Date() ein neues Datumsobjekt mit der aktuellen Zeit (engl. currentTime) erzeugt.

Mittels style.setProperty() wird dann die aktuelle Zeit den CSS-Variablen zugewiesen.

Hauptartikel: SVG-Uhr

CSS-Eigenschaften auslesen

Das style-Objekt wird immer wieder missverstanden: Sie können damit lediglich Inline-Styles setzen und die bereits gesetzten auslesen, aber nicht den berechneten Wert einer CSS-Eigenschaft auslesen.

Die besagten Objekteigenschaften (.style.cssEigenschaft) sind allesamt leer, wenn sie nicht im betreffenden HTML-Element über ein style-Attribut oder wie beschrieben mit JavaScript gesetzt wurden:

auslesen von Inline-Styles
<p id="ohne-inline-styles">Element ohne Inline-Styles</p>
<p id="mit-inline-styles" style="color: red">Element mit Inline-Styles</p>
// Gibt einen leeren String aus:
console.log(
    document.getElementById('ohne-inline-styles').style.backgroundColor
);
// Gibt »red« aus, weil Inline-Style gesetzt wurde:
console.log(
    document.getElementById('mit-inline-styles').style.backgroundColor
);

getComputedStyle

Das Auslesen des gegenwärtigen Werts von CSS-Eigenschaften eines Elements gestaltet sich als schwierig. Wenn man in Erfahrung bringen will, welche tatsächliche Textfarbe oder welche Pixel-Breite ein Element hat, dann ist nach den sogenannten berechneten Werten (englisch: computed values) gefragt, wie sie in der CSS-Fachsprache genannt werden.

Im Allgemeinen besteht der aktuelle CSS-Eigenschaftswert eines Elements aus einer Kombination von

  • Standardformatierungen des Browsers
  • (oft mehreren) im CSS festgelegten Regelsätzen, die über Selektoren ausgewählt werden und dann nach den Regeln der Kaskade kombiniert und überschrieben werden.

Die Methode window.getComputedStyle() enthält für jede CSS-Eigenschaft eine entsprechende Objekteigenschaft mit dem aktuellen berechneten Wert.

Ausgabe der berechnenten Werte von CSS-Eigenschaften
let element = document.getElementById('beispielID');
let computedStyle = window.getComputedStyle(element, null);
console.log('Textfarbe: ' + computedStyle.color);
console.log('Elementbreite: ' + computedStyle.width);

Sie erwartet ein Elementobjekt als ersten Parameter und einen String mit einem CSS-Pseudo-Element als zweiten Parameter (beispielsweise "after", "before", "first-line" oder "first-letter"). Wenn man nicht die Formatierung des Pseudo-Elements abfragen will, übergibt man schlichtweg null als zweiten Parameter.

Die berechneten Werte (computed values), die getComputedStyle zurückgibt, sind nicht in jedem Fall identisch mit den Werten, die Sie im Stylesheet notiert haben. Der Längenwert in margin-top: 2em; und der Prozentwert in font-size: 120%; werden von den verbreiteten grafischen Browsern letztlich in Pixelwerte umgerechnet, sodass getComputedStyle Werte mit der Einheit px zurückgibt.

Auch beispielsweise bei Farbwerten können Sie nicht erwarten, dass das Format des berechneten Wertes mit dem des Stylesheets übereinstimmt. Denn es gibt in CSS verschiedene Formate, um Farbwerte zu notieren. Notieren Sie im Stylesheet beispielsweise color: red, so kann es sein, dass getComputedStyle für color den Wert "rgb(255 0 0)" liefert. Dies ist derselbe Wert in einer alternativen Schreibweise.

Elementbox-Größen auslesen

Über die vorgestellten Techniken ist es nicht browserübergreifend möglich, die aktuelle Höhe und Breite einer Element-Box in der Einheit Pixel auszulesen. Stattdessen sollten Sie folgende Eigenschaften der Elementobjekte verwenden. Sie wurden ursprünglich von Microsoft erfunden, erfreuen sich aber breiter Browser-Unterstützung. Im Gegensatz zu getComputedStyle geben sie keine String-Werte samt Einheiten zurück, sondern direkt JavaScript-Zahlen (Number-Werte) in der Einheit Pixel.

  • offsetWidth und offsetHeight: liefern die Breite bzw. Höhe der Rahmen-Box des Elements. Das bedeutet, dass der Innenabstand (padding) und der Rahmen (border) inbegriffen sind, der Außenrahmen hingegen (margin) nicht.
  • clientWidth und clientHeight: liefern Breite bzw. Höhe der Innenabstand-Box des Elements. Das bedeutet, dass padding inbegriffen ist, während border und margin nicht eingerechnet werden. Ebenso wird die Größe einer möglicherweise angezeigte Bildlaufleiste (Scrollbar) nicht einberechnet.
  • scrollWidth und scrollHeight: geben die tatsächlich angezeigte Breite bzw. Höhe des Inhalts wieder. Wenn das Element kleiner ist, als der Inhalt es erfordert, also Bildlaufleisten angezeigt werden, so geben diese Eigenschaften die Größe des aktuell sichtbaren Ausschnittes wieder.

In den meisten Fällen werden Sie die äußere Größe eines Elements benötigen, also offsetWidth und offsetHeight. Das folgende Beispiel gibt die Größe eines Elements aus:

Beispiel
let element = document.getElementById('beispielID');
console.log('Breite: ' + element.offsetWidth + '\nHöhe: ' + element.offsetHeight);

Falls sie die innere Größe benötigen, so können Sie zunächst die aktuellen Werte der jeweiligen padding-Eigenschaften auslesen. Das sind padding-left und padding-right für die Breite bzw. padding-top und padding-bottom für die Höhe. Diese subtrahieren sie von offsetWidth bzw. offsetHeight, um die tatsächliche Innengröße zu erhalten.


Empfehlung: Verwenden Sie diese Methoden nur gezielt und sparsam!
Meist ist es nicht nötig, diese Werte zu ermitteln, da sich ein responsives Layout unabhängig von genauen Pixelangaben an den Viewport anpasst.
Wenn Sie aktuelle Pixelwerte benötigen, um Ihr Layout mit JavaScript zu steuern, haben Sie schon vorher konzeptuelle Fehler eingebaut!

Siehe auch

  1. JavaScript und CSS
    1. JavaScript und CSS/Stylesheets dynamisch ändern
    2. JavaScript und CSS/Stylesheets dynamisch wechseln


Quellen

  1. SELF-Forum: Eigenschaften in CSS-Klassen mit JavaScript ändern vom 21.06.2020
  2. Mathias Schäfer: JavaScript: Zusammenarbeit mit CSS, Darstellung von Dokumenten steuern