CSS Objektmodell (CSSOM)

Januar 2024
Das CSSOM besteht aus einer Reihe von Objekten, deren verfügbare Eigenschaften und Methoden auch als API bezeichnet werden. Wir gehen in diesem Beitrag auf Entdeckungsreise um diese Objekte kennenzulernen und bauen dazu Stück für Stück ein Beispieldokument auf.
Wir brauchen dazu einen lokalen Webserver, z.B. Apache von apachefriends.org. Auf meinem Server habe ich das Verzeichnis cssom unter htdocs (DocumentRoot) angelegt und darin das Beispieldokument aufgebaut.
Weiterhin brauchen wir eine Beschreibung der CSSOM-Objekte, ich gebe in der Folge passende Links zur Online-Dokumentation MDN an.

Ein vorbereitetes HTML-Dokument

<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CSSOM</title>

  <link rel="stylesheet" type="text/css" href="page.css"> 

  <style>
    body { background-color: #fff; }
    h3  { color: red; }
  </style>

</head>
<body>

<h3>Versuche mit CSSOM</h3>

  <p>Diese Seite funktioniert nur im Server-Zugriff korrekt.</p>

  <div>Display:<br>
    <!-- Hier werden die Ausgaben eingefügt -->
    <pre id="display" style="border: 1pt solid green;"></pre>
  </div>

<script>
"use strict";
const myDisplay = document.getElementById('display');

  // Hier kommt unser JavaScript-Code rein.

</script>

</body>
</html>
Dieses Dokument mit Namen cssom-example.html ist unser Versuchsobjekt und wird auch die Ausgaben anzeigen.
Es ist Absicht, dass sowohl eine CSS-Datei eingebunden wird, als auch CSS-Code in ein style-Element gepackt wurde. Und das Styling für unser Display (Textausgabe) befindet sich in einem style-Attribut.

Hier der CSS-Code in der Datei page.css

body {
  font-family:sans-serif;
  font-size:1.4vw;
}

h3 { text-decoration: underline; }
Der JavaScript-Code im script-Element ist nicht gerade eine professionelle Lösung, wurde aber der Übersichtlichkeit halber so gewählt.
HTML- und CSS-Datei werden wir nicht weiter verändern, also brauchen wir in der Folge nur unseren JavaScript-Code aufzuzeigen, der an die benannte Stelle im HTML-Dokument eingefügt wird.

Das Tor zum CSSOM

Wir beginnen unser JavaScript-Programm mit einer Abfrage von document.styleSheets, denn das ist sozusagen das Tor zum CSSOM.
const myList = document.styleSheets; 
myDisplay.textContent += '- document.styleSheets liefert ein ' + myList + '\n';
myList enthält jetzt das, was document.styleSheets zurückgibt. Es ist nicht zu erwarten, dass dies ein String ist, wir geben es trotzdem wie einen String aus. Freundlicherweise erhalten wir eine Ausgabe im Display, die besagt, um welches Objekt es sich handelt:
- document.stylesheets liefert ein [object StyleSheetList]

Die StyleSheet-Liste

Was wir mit diesem Objekt anfangen können, erklärt uns das MDN: interface StyleSheetList. Dort finden wir u.a., dass es die Eigenschaft length und die Methode item() gibt. Das ist naheliegend, denn wenn unser Objekt eine Liste ist, dann sollte es Einträge (Items) geben. Das MDN verrät uns auch, dass die Einträge Objekte vom Typ CSSStyleSheet sind.
Wir setzen unseren Code mit dieser Schleife fort:
myDisplay.textContent += `  Anzahl Objekte von myList.length: ` + myList.length + '\n';
for (let s = 0; s < myList.length; s++)
{
  let myStyleSheet = myList.item(s); // CSSStyleSheet
  myDisplay.textContent += `    StyleSheetList.item(${s}) ist ein ` + myStyleSheet + '\n';
}
Nach Reload unseres Dokuments erweitert sich die Anzeige um
  Anzahl Objekte von myList.length: 2
    StyleSheetList.item(0) ist ein [object CSSStyleSheet]
    StyleSheetList.item(1) ist ein [object CSSStyleSheet]

Objekt vom Typ CSSStyleSheet

Aber was für Stylesheets sind das? Wir befragen das MDN: CSSStyleSheet. Die Eigenschaften und Methoden sagen etwas über enthaltene Regeln aus, aber wir bräuchten eher so etwa wie einen Namen.
Gleich am Anfang der Referenzseite ist dargestellt, dass das Interface CSSStyleSheet vom Interface StyleSheet erbt. Schauen wir uns doch mal dieses an (MDN: interface StyleSheet).
Und da finden wir die Eigenschaft href, die uns den Ort (the location) des StyleSheets verraten soll. Erweitern wir unsere Schleife also mit der Ausgabe der vererbten Eigenschaft href:
for (let s = 0; s < myList.length; s++)
{
  let myStyleSheet = myList.item(s); /// CSSStyleSheet
  myDisplay.textContent += `    StyleSheetList.item(${s}) ist ein ` 
                        + myStyleSheet + '\n';
  myDisplay.textContent += `    CSSStyleSheet.href referenziert: `  
                        + myStyleSheet.href + '\n';

  /* an diese Stelle kommt der weiter unten folgende Code hin. */
}
Nun erhalten wir folgende Ausgabe:
  Anzahl Objekte von StyleSheetList.length: 2
    StyleSheetList.item(0) ist ein [object CSSStyleSheet]
    CSSStyleSheet.href referenziert: http://localhost/cssom/page.css

    StyleSheetList.item(1) ist ein [object CSSStyleSheet]
    CSSStyleSheet.href referenziert: null
Und jetzt wird es klar: Das erste Objekt entspricht dem StyleSheet unserer CSS-Datei page.css.. Das zweite Objekt hat keinen Ort. Es kann sich aber um unseren CSS-Code im <style>-Abschnitt unseres HTML-Dokuments handeln. Das werden wir sehen, wenn wir das Objekt CSSStyleSheet weiter aufdröseln.

Mal wieder eine Liste: CSSRuleList

Wir suchen also in der MDN-Referenz zu CSSStyleSheet nach etwas, was auf den Inhalt hindeutet. Ein StyleSheet enthält CSS-Regeln, und da finden wir die Eigenschaft cssRules, die diese in einer Liste enthalten soll.
Wir erweitern unseren Code in der Schleife an der gekennzeichneten Stelle:
  let myRuleList = myStyleSheet.rules; // CSSRuleList
  myDisplay.textContent += `  - CSSStyleSheet.rules ist ein ` + myRuleList + '\n\n'; 

  /* hier der spätere Code */
Und nun sieht die Ausgabe unserer Schleife so aus:
  Anzahl Objekte von StyleSheetList.length: 2
    StyleSheetList.item(0) ist ein [object CSSStyleSheet]
    CSSStyleSheet.href referenziert: http://localhost/cssom/page.css
  - CSSStyleSheet.rules ist ein [object CSSRuleList]

    StyleSheetList.item(1) ist ein [object CSSStyleSheet]
    CSSStyleSheet.href referenziert: null
  - CSSStyleSheet.rules ist ein [object CSSRuleList]
Wir erhalten mal wieder eine Liste, die die CSS-Regeln enthalten sollten. Das erfahren wir im MDN: CSSRuleList. Wie bei den meisten Listen gibt es auch hier length und item(), so dass wir mit einer zweiten Schleife die Einträge darstellen können.

Endlich bei den Regeln angekommen

Die Einträge in CSSRuleList sind Objekte vom Typ CSSStyleRule, und die haben die Eigenschaft cssText, die wir in der neuen Schleife gleich mit ausgeben.
Wir setzen unseren Code mit folgender Passage fort:
  for (let i = 0; i < myRuleList.length; i++)
  {
     let myStyleRule = myRuleList.item(i); // CSSStyleRule
     myDisplay.textContent += `    - CSSRuleList.item(${i}) ist ein ` 
                           + myStyleRule + '\n'; // CSSStyleRule
     myDisplay.textContent += `      Eine CSS-Regel in CSSStyleRule.cssText: ` 
                           + myStyleRule.cssText + '\n'; 

     /* hier der weitere Code */
  }
Die Ausgabe der Schleife für das erste Stylesheet (page.css, vergleiche CSS-Code):
- CSSRuleList.item(0) ist ein [object CSSStyleRule]
  Eine CSS-Regel in CSSStyleRule.cssText: 
    body { font-family: sans-serif; font-size: 1.4vw; }

- CSSRuleList.item(1) ist ein [object CSSStyleRule]
  Eine CSS-Regel in CSSStyleRule.cssText: h3 { text-decoration: underline; }
Und die Ausgabe der Schleife für das zweite Stylesheet (der <style>-Abschnitt, vergleiche HTML-Dokument)
- CSSRuleList.item(0) ist ein [object CSSStyleRule]
  Eine CSS-Regel in CSSStyleRule.cssText: 
    body { background-color: rgb(255, 255, 255); }

- CSSRuleList.item(1) ist ein [object CSSStyleRule]
  Eine CSS-Regel in CSSStyleRule.cssText: h3 { color: red; }
Die Eigenschaften von CSSStyleRule sind natürlich in MDN: interface CSSStyleRule beschrieben. Die Eigenschaft cssText ist jedoch nicht gleich zu finden. CSSStyleRule erbt von CSSGroupingRule und dieses Interface wiederum von CSSRule. Und in CSSRule finden wir die Eigenschaft cssText.

Und weiter bis zu jedem einzelnen Wert

CSSStyleRule besitzt auch die Eigenschaften selectorText und style. Schauen wir doch mal, was sich dahinter verbirgt. Wir erweitern die zweite Schleife um die Ausgabe dieser Eigenschaften.
     let mySelector = myStyleRule.selectorText; // String
     myDisplay.textContent += `      CSSStyleRule.selectorText ist: ` 
                           + mySelector  + '\n';
     let myStyleDeclaration = myStyleRule.style;  // CSSStyleDeclaration
     myDisplay.textContent += `    - CSSStyleRule.style ist ein ` 
                           + myStyleDeclaration + '\n\n';
Die Ausgabe der zweiten Schleife sieht jetzt wie folgt aus. Für das erste Stylesheet:
- CSSRuleList.item(0) ist ein [object CSSStyleRule]
  Eine CSS-Regel in CSSStyleRule.cssText: 
    body { font-family: sans-serif; font-size: 1.4vw; }
  CSSStyleRule.selectorText ist: body
  - CSSStyleRule.style ist ein [object CSSStyleDeclaration]

- CSSRuleList.item(1) ist ein [object CSSStyleRule]
  Eine CSS-Regel in CSSStyleRule.cssText: h3 { text-decoration: underline; }
  CSSStyleRule.selectorText ist: h3
- CSSStyleRule.style ist ein [object CSSStyleDeclaration]
und für das zweite Stylesheet:
- CSSRuleList.item(0) ist ein [object CSSStyleRule]
  Eine CSS-Regel in CSSStyleRule.cssText: 
    body { background-color: rgb(255, 255, 255); }
  CSSStyleRule.selectorText ist: body
- CSSStyleRule.style ist ein [object CSSStyleDeclaration]

- CSSRuleList.item(1) ist ein [object CSSStyleRule]
  Eine CSS-Regel in CSSStyleRule.cssText: h3 { color: red; }
  CSSStyleRule.selectorText ist: h3
- CSSStyleRule.style ist ein [object CSSStyleDeclaration]
Selektoren werden über die Eigenschaft cssText als Zeichenkette ausgegeben. Das es aber zu einem Sektor mehrere Regeln geben kann, sind diese wiederum in einem Objekt mit dem Namen CSSStyleDeclaration versteckt.
Kleiner Schönheitsfehler: Der Firefox zeigt für anstatt CSSStyleDeclaration den Namen CSS2Properties an. Näherungsweise können wir aber annehmen, dass es ein Objekt mit gleichen Eigenschaften ist.
Um also an die einzelnen Werte herabzukommen, müssen wir das Objekt CSSStyleDeclaration aufdröseln. Da es sich wie einen Liste verhält (siehe MDN: interface CSSStyleDeclaration), gibt es die Eigenschaft length und die Methode item. Das heißt, wir programmieren eine dritte Schleife.
myDisplay.textContent += `      Anzahl Properties in CSSStyleDeclaration.length: `
                      + myStyleDeclaration.length + '\n';
for (let d = 0; d < myStyleDeclaration.length; d++)
{
   myDisplay.textContent += `        CSSStyleDeclaration.item(${d}) : `
                         + myStyleDeclaration.item(d) + ', ';
   myDisplay.textContent += 'sein Wert: ' 
           + myStyleDeclaration.getPropertyValue(myStyleDeclaration.item(d))
           + '\n';
}
myDisplay.textContent += '\n'; // der Optik wegen
Wie wir sehen werden, liefert item(index) nur den Namen einer Eigenschaft, den Wert müssen wir uns über die Methode getPropertyValue(Name ) holen.
Auch hier gibt Unterschiede bei den Browsern. Während Chrome bei einigen Werten initial anzeigt, was ja auch zutrifft, zeigt Firefox die Initial-Werte an. Das dürfte sich zukünftig angleichen.
- document.styleSheets liefert ein [object StyleSheetList]

  Anzahl Objekte von StyleSheetList.length: 2
    StyleSheetList.item(0) ist ein [object CSSStyleSheet]
    CSSStyleSheet.href referenziert: http://localhost/cssom/page.css
  - CSSStyleSheet.rules ist ein [object CSSRuleList]

    Anzahl Objekte in CSSRuleList.length: 2
    - CSSRuleList.item(0) ist ein [object CSSStyleRule]
      Eine CSS-Regel in CSSStyleRule.cssText: 
        body { font-family: sans-serif; font-size: 1.4vw; }
      CSSStyleRule.selectorText ist: body
    - CSSStyleRule.style ist ein [object CSSStyleDeclaration]
      Anzahl Properties in CSSStyleDeclaration.length: 2
        CSSStyleDeclaration.item(0) : font-family, sein Wert: sans-serif
        CSSStyleDeclaration.item(1) : font-size, sein Wert: 1.4vw

    - CSSRuleList.item(1) ist ein [object CSSStyleRule]
      Eine CSS-Regel in CSSStyleRule.cssText: h3 { text-decoration: underline; }
      CSSStyleRule.selectorText ist: h3
    - CSSStyleRule.style ist ein [object CSSStyleDeclaration]
      Anzahl Properties in CSSStyleDeclaration.length: 4
        CSSStyleDeclaration.item(0) : text-decoration-line, sein Wert: underline
        CSSStyleDeclaration.item(1) : text-decoration-thickness, sein Wert: initial
        CSSStyleDeclaration.item(2) : text-decoration-style, sein Wert: initial
        CSSStyleDeclaration.item(3) : text-decoration-color, sein Wert: initial

    StyleSheetList.item(1) ist ein [object CSSStyleSheet]
    CSSStyleSheet.href referenziert: null
  - CSSStyleSheet.rules ist ein [object CSSRuleList]

    Anzahl Objekte in CSSRuleList.length: 2
    - CSSRuleList.item(0) ist ein [object CSSStyleRule]
      Eine CSS-Regel in CSSStyleRule.cssText: 
        body { background-color: rgb(255, 255, 255); }
      CSSStyleRule.selectorText ist: body
    - CSSStyleRule.style ist ein [object CSSStyleDeclaration]
      Anzahl Properties in CSSStyleDeclaration.length: 1
  
        CSSStyleDeclaration.item(0) : background-color, sein Wert: rgb(255, 255, 255)

    - CSSRuleList.item(1) ist ein [object CSSStyleRule]
      Eine CSS-Regel in CSSStyleRule.cssText: h3 { color: red; }
      CSSStyleRule.selectorText ist: h3
    - CSSStyleRule.style ist ein [object CSSStyleDeclaration]
      Anzahl Properties in CSSStyleDeclaration.length: 1
        CSSStyleDeclaration.item(0) : color, sein Wert: red