JavaScript: Zusammenarbeit mit CSS, Darstellung von Dokumenten steuern

Einleitung

Mit JavaScript können Sie die Darstellung des Dokuments dynamisch ändern, während es im Browser angezeigt wird. Dies ist ein wesentlicher Bestandteil der Interaktivität, die Sie einem Dokument mittels Javascript hinzufügen können. Die Möglichkeiten sind vielfältig: Beispielsweise können Sie als Reaktion auf eine Benutzereingabe gewisse Elemente ein- und ausblenden. Es sind aber auch – mit entsprechendem Aufwand – visuelle Effekte und komplexe Animationen möglich.

Die Programmiersprache JavaScript besitzt keine eigenen Techniken, um die Gestaltung einer Webseite zu beeinflussen. Vielmehr besitzt JavaScript eine Schnittstelle zur Formatierungssprache Cascading Stylesheets (CSS). Mittels JavaScript können Sie also sämtliche Formatierungen vornehmen, die CSS möglich macht. Daher sollten Sie die Grundlagen von CSS bereits beherrschen, bevor Sie Dokumente mittels JavaScript umformatieren.

Das dynamische Ändern der Darstellung bildet einen großen Komplex in der JavaScript-Programmierung - ein anderer ist das dynamische Ändern der Inhalte über das Document Object Model (DOM). Über das DOM können Sie Elemente hinzufügen oder löschen, Attributwerte setzen und Textinhalte einfügen oder verändern. In der Praxis gehen diese beiden Aufgaben - den Inhalt und dessen Darstellung modifizieren - oft miteinander einher.

Trennung von Layout-Regeln und JavaScript-Logik

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

Wenn Sie bereits mit CSS fortgeschritten sind und einige Layouts mit CSS umgesetzt haben, sollten Sie die Aufgaben der Webtechniken kennen und deren sinnvolle Anwendung bereits beherrschen:

Diese Arbeitsweise hat bringt Ihnen enorme Vorteile bei der Webseiten-Entwicklung. Inhalte und Präsentation können unabhängig voneinander schnell geändert werden, mit wenig Aufwand kann die gewünschte Präsentation erzielt werden.

Wenn nun die dritte Technik – JavaScript – hinzutritt, sollten Sie dieses Modell konsequent fortführen. Orientieren Sie sich an folgenden Faustregeln:

Sie können nicht nur ausgelagerte Stylesheet-Regeln auf ein Element anwenden, sondern auch direkt gewisse CSS-Eigenschaften von einzelnen Elementen ändern können. 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 einfache Methode, um die Darstellung von Elementen per JavaScript zu ändern, ist das Setzen einer Klasse. Die Formatierungen für diese Klasse wird im Stylesheet untergebracht. Damit wird der empfohlenen Trennung vom JavaScript-Code Genüge getan.

Betrachten wir als Beispiel ein JavaScript, dass die Eingaben eines Formulars überprüft. Beim Absenden des Formulars wird eine Handler-Funktion für das Ereignis submit aktiv. Diese Funktion soll fehlerhafte Formularfelder rot markieren.

Nehmen wir an, die input-Eingabefelder sind standardmäßig im Stylesheet so formatiert:

input {
    padding: 4px 6px;
    border: 1px solid #555;
    background-color: #fafafa;
}

Im Stylesheet wird nun eine Regel definiert mit den Eigenschaften für fehlerhafte Felder. Wir nutzen dazu einen Selektor mit dem Elementnamen input kombiniert mit der Klasse fehlerhaft:

input.fehlerhaft {
   border-color: red;
   background-color: #fff8f5;
}

Am Anfang trifft der Selektor input.fehlerhaft auf kein Feld im Dokument zu – solange nicht ein Feld die Klasse fehlerhaft besitzt. Um ein Eingabefeld umzuformatieren und die Regel anzuwenden, vergeben wir dynamisch diese Klasse an das gewünschte input-Element.

Die Klassen eines Elements sind in JavaScript über die Eigenschaft className des entsprechenden Elementobjektes zugänglich. Sie können diese Eigenschaft sowohl auslesen als auch ihr einen neuen Wert zuweisen. Das vereinfachte Schema zum Setzen einer Klasse lautet:

element.className = 'klassenname';

In der beispielhaften Formularüberprüfung kann das Setzen der Klasse folgendermaßen aussehen: Wir definieren eine Funktion formularÜberprüfung, die als Handler für das Ereignis submit registriert wird (siehe Ereignisverarbeitung). In dieser Funktion wird das zu überprüfende Formularfeld über das DOM mittels document.getElementById herausgesucht. Ist der Wert des Feldes leer, wird eine Meldung ausgegeben und das Feld bekommt die Klasse fehlerhaft.

function formularÜberprüfung () {
    // Spreche das Formularfeld über das DOM an und
    // speichere es in eine Variable zwischen:
    var element = document.getElementById("kontaktformular-name");
    // Prüfe den Feldwert:
    if (element.value == "") {
        // Zeige im Fehlerfall ein Hinweisfenster:
        window.alert("Bitte geben Sie Ihren Namen an.");
        // Weise dem Element die Klasse »fehlerhaft« zu:
        element.className = 'fehlerhaft';
        // Setze den Fokus auf das Feld:
        element.focus();
        // Verhindere das Absenden des Formulars
        // (unterdrücke die Standardaktion des Ereignisses):
        return false;
    }
}

function init () {
    // Starte die Ereignis-Überwachung mittels
    // traditionellem Event-Handling
    document.getElementById("kontaktformular").onsubmit = formularÜberprüfung;
}

window.onload = init;

Der zugehörige HTML mit den nötigen IDs könnte so aussehen:

<form action="…" method="post" id="kontaktformular">
    <p><label>
        Ihr Name:
        <input type="text" name="name" id="kontaktformular-name">
    </label></p>
    … weitere Felder …
    <p><input type="submit" value="Absenden"></p>
</form>

Der Clou dieser Vorgehensweise ist, dass Sie mit dem Setzen der Klasse an einem Element nur eine minimale JavaScript-Änderung 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.

Über dieses Modell können Sie auch komplexere Aufgabenstellungen lösen, denn Ihnen stehen alle Möglichkeiten von CSS-Selektoren zu 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.

TODO: Beispiel dazu. Mit Nachfahrenselektoren größere Umformatierungen vornehmen, ohne alle Elemente einzeln anzusprechen

Komfortables Hinzufügen, Löschen und Abfragen von Klassen

Die oben vorgestellte Methode zum Setzen der Klasse ist stark vereinfacht und hat verschiedene Nachteile. Ein HTML-Element kann nämlich mehreren Klassen angehören. Die JavaScript-Eigenschaft className enthält dann eine Liste von Klassen, die durch Leerzeichen getrennt werden.

Beispielsweise kann im HTML <input class="klasse1 klasse2"> notiert sein. Wenn Sie nun mittels JavaScript eine dritte Klasse hinzufügen wollen, so können Sie nicht einfach element.className = "klasse3" notieren, denn dies würde die ersten beiden Klassen löschen. Dasselbe gilt für das Entfernen einer Klasse: Wenn Sie einfach den Attributwert mit element.className = "" leeren, dann löschen Sie alle Klassenzugehörigkeiten.

Aus diesem Grund sollten Sie nicht direkt mit der className-Eigenschaft arbeten, sondern für diese Aufgaben Helferfunktionen verwenden, die mehrere Klassen berücksichtigen. Die meisten Allround-Bibliotheken bieten entsprechenden Funktionen an. Üblicherweise tragen sie folgende Namen:

Falls Sie keine Fertigbibliothek nutzen, können Sie diese Helferfunktionen dennoch in ihre Scripte aufnehmen. Eine mögliche Umsetzung als lose globale Funktionen sieht folgendermaßen aus:

function addClass (element, className) {
    if (!hasClass(element, className)) {
        if (element.className) {
            element.className += " " + className;
        } else {
            element.className = className;
        }
    }
}

function removeClass (element, className) {
var regexp = addClass[className];
    if (!regexp) {
        regexp = addClass[className] = new RegExp("(^|\\s)" + className + "(\\s|$)");
    }
    element.className = element.className.replace(regexp, "$2");
}

function hasClass (element, className) {
    var regexp = addClass[className];
    if (!regexp) {
        regexp = addClass[className] = new RegExp("(^|\\s)" + className + "(\\s|$)");
    }
    return regexp.test(element.className);
}

function toggleClass (element, className) {
    if (element.hasClass(className)) {
        element.removeClass(className);
    } else {
        element.addClass(className);
    }
}

Alle Funktionen erwarten jeweils zwei Parameter, nämlich das Elementobjekt und den gewünschten Klassennamen als String. Folgende Beispiele sollen die Anwendung illustrieren:

// Element ansprechen und Elementobjekt in einer Variable zwischenspeichern:
var element = document.getElementById("beispielID");
// Klasse hinzufügen:
addClass(element, "beispielklasse");
// Klasse löschen:
removeClass(element, "beispielklasse");
// Klasse an- und ausschalten je nach vorherigem Status:
toggleClass(element, "beispielklasse");
// Vorhandensein einer Klasse prüfen:
if (hasClass(element, "beispielklasse")) {
    window.alert("Klasse gefunden.");
} else {
    window.alert("Klasse nicht gefunden.");
}

Mit diesen Funktionen in Ihrem JavaScript-Werkzeugkasten können Sie das Zusammenspiel von JavaScript-Interaktivität und Stylesheet-Formatierungen komfortabel und übersichtlich meistern.

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:

<p style="color: red; background-color: yellow; font-weight: bold;">Fehler!</p>

Gegenüber dem Einsatz von zentralen Formaten in Stylesheets sind diese sogenannten Inline-Styles (eingebettete Formatierungen) ineffektiv und führen zu 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.

CSS-Eigenschaftsnamen mit Bindestrichen, wie z.B. background-color, können nicht unverändert als JavaScript-Eigenschaftsnamen übernommen werden. Deshalb werden sie im sogenannten Camel-Case (Groß- und Kleinschreibung im Kamel-Stil) 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 Höcker eines Kamels verglichen.

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

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:

element.style.marginTop = 15; // Falsch!
element.style.marginTop = "15px"; // Richtig

Sonderfälle bei der Umsetzung von CSS- in JavaScript-Eigenschaftsnamen

Abweichungen vom besagten Schema

cssFloat vs. styleFloat

Sinnvoller Einsatz des style-Objektes

Das Setzen von CSS-Formatierungen direkt über das style-Objekt ist zwar einfach. Doch Sie diese Präsentationsregeln wie gesagt nicht im JavaScript, sondern sie 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.

style ist nicht zum Auslesen der gegenwärtigen Eigenschaftswerte geeignet

Das style-Objekt wird immer wieder missverstanden: Sie können über das style-Objekt nicht den aktuellen, berechneten Wert einer CSS-Eigenschaft auslesen. Sie können damit lediglich Inline-Styles setzen und die bereits gesetzten 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. Folgendes Beispiel verdeutlicht dies:

<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:
window.alert(
    document.getElementById("ohne-inline-styles").style.backgroundColor
);
// Gibt »red« aus, weil Inline-Style gesetzt wurde:
window.alert(
    document.getElementById("mit-inline-styles").style.backgroundColor
);

CSS-Eigenschaften auslesen

Über das style-Objekt besteht wie gesagt kein Zugriff auf den aktuellen CSS-Eigenschaftswert eines Elements, sofern kein entsprechender Inline-Style gesetzt wurde. Dies ist meistens der Fall, denn die Formatierungen gehen üblicherweise auf zentrale Formatierungen in ausgelagerten Stylesheets und auf die Standarformatierungen des Browsers zurück.

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.

getComputedStyle aus W3C DOM CSS

Der W3C-Standard, der die Schnittstelle zwischen JavaScript und CSS und damit der Darstellung eines Dokuments festlegt, kennt zu diesem Zweck die Methode window.getComputedStyle(). 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.

getComputedStyle gibt ein Objekt zurück, das genauso aufgebaut ist wie das bereits besprochene element.style-Objekt. Es enthält für jede CSS-Eigenschaft eine entsprechende Objekteigenschaft mit dem aktuellen berechneten Wert.

var element = document.getElementById('beispielID');
var computedStyle = window.getComputedStyle(element, null);
window.alert("Textfarbe: " + computedStyle.color);
window.alert("Elementbreite: " + computedStyle.width);

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.

Microsofts currentStyle

getComputedStyle wird von allen großen Browsern unterstützt. Der Internet Explorer kennt die Methode jedoch erst ab Version 9. Browserübergreifende Scripte, die ältere Internet Explorer unterstützen, müssen daher eine Sonderlösung für den IE einbauen.

Microsoft bietet eine Alternative, die ähnliches leistet: Jedes Elementobjekt kennt neben dem angesprochenen style-Objekt ein gleich aufgebautes Objekt namens currentStyle mit Objekteigenschaften für jede unterstützte CSS-Eigenschaft. Im Gegensatz zum style-Objekt erlaubt currentStyle das Auslesen des aktuellen berechneten CSS-Eigenschaftswertes:

var element = document.getElementById('beispielID');
var currentStyle = element.currentStyle;
window.alert("Textfarbe: " + currentStyle.color);
window.alert("Elementbreite: " + currentStyle.width);

currentStyle liefert meist ein ähnliches Ergebnis wie das standardisierte getComputedStyle. In manchen Fällen gibt es jedoch Abweichungen, etwa im obigen Beispiel beim Auslesen des width-Wertes. currentStyle.width gibt auto zurück, wenn dem Element keine explizite Breite zugewiesen wurde. Für das browserübergreifende Auslesen der Box-Größe eignen sich stattdessen die Eigenschaft offsetWidth/offsetHeight sowie clientWidth/clientHeight.

Browserübergreifendes Auslesen von CSS-Eigenschaften

Durch Kombination von getComputedStyle für standardkonforme Browser und currentStyle für ältere Internet Explorer können wir eine lose Helferfunktion schreiben, die uns den aktuellen CSS-Eigenschaftswert liefert. Die Funktion fragt ab, welches Objekt zur Verfügung steht, und bringt den Wert damit in Erfahrung:

function getStyleValue (element, cssProperty) {
    var value = "";
    if (window.getComputedStyle) {
        value = window.getComputedStyle(element, null)[cssProperty];
    } else if (element.currentStyle) {
        value = element.currentStyle[cssProperty];
    }
    return value;
}

Bei der Anwendung wird der Funktion das Elementobjekt und ein String übergeben, der den Eigenschaftsnamen in der JavaScript-typischen Schreibweise enthält:

var currentFontSize = getStyleValue(document.getElementById("beispielID"), "fontSize");
window.alert(currentFontSize);

Dies funktioniert zwar browserübergreifend, allerdings ist das Ergebnis unterschiedlich: Ältere Internet Explorer, in welchen nur currentStyle zur Verfügung steht, geben für die font-size-Eigenschaft einen pt-Wert zurück, andere Browser einen px-Wert. Mit diesen Browserunterschieden müssen Sie rechnen, sie lassen sich nicht einfach vereinheitlichen.

Elementbox-Größen auslesen über Microsoft-Eigenschaften

Ü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 und currentStyle 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 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:

var element = document.getElementById('beispielID');
window.alert("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 substrahieren sie von offsetWidth bzw. offsetHeight, um die tatsächliche Innengröße zu erhalten.

Zugriff auf die eingebundenen Stylesheets

Stylesheets mit JavaScript deaktivieren und einfügen

Stylesheet-Regeln dynamisch erzeugen und ändern

Stylesheet-Regeln dynamisch erzeugen und ändern