JavaScript/Tutorials/Grundlagen des DOM

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Mithilfe von JavaScript, das in jedem Browser läuft, werden Webseiten interaktiv. Sie können auf Benutzereingaben reagieren, Anfragen an den Server schicken und weitere Inhalte abrufen.

In diesem Tutorial lernen Sie die grundlegenden Techniken, wie Sie mit Hilfe von JavaScript und des DOMs gezielt Elemente Ihrer Webseite ansprechen und durch Anwenderereignisse Scripte starten, die dann interaktiv sowohl Inhalte als auch Layout verändern. Anschließend werden die erworbenen Kenntnisse anhand eines kleinen Browser-Spiels vertieft.

Voraussetzung sind die Tutorials

Was ist das DOM?[Bearbeiten]

Das DOM ist eigentlich eine Schnittstelle zwischen JavaScript und HTML-Dokumenten, sein Name Document Object Model gründet sich auf das ihr zugrundeliegende Objektmodell.

Ein HTML-Dokument besteht ja eigentlich nur aus reinem Text, der erst vom Browser ausgelesen und geparst wird. Die einzelnen Elemente werden so in einer Baumstruktur als Unterobjekte des window-Objekts dargestellt.

DOM - Baumstruktur einer Webseite

Mit JavaScript können Sie nun beliebige Elemente im Elementbaum ansprechen, sie verändern oder entfernen und mit Anwenderereignissen verknüpfen.

Elementknoten ansprechen[Bearbeiten]

Erinnern Sie sich noch an unser "Hallo Welt"-Beispiel? Die Nachteile einer Textausgabe mittels alert sind vor allem die Speicherbelastung des Browsers sowie die mangelnde Gestaltungsmöglichkeit des Ausgabefensters.

Beispiel ansehen …
<output id="info"></output>


<script>
  'use strict';
  var text = 'Hallo Welt!';
  
  document.getElementById('info').innerText = text;
  
</script>
Das Script ist nun in eine Webseite eingebunden. Teil dieser Webseite ist ein (noch) leeres output-Element mit der id info.

Anstelle der Ausgabe mit alert wird mit document.getElementById das output-Element über seine id info angesprochen und mit innerText mit dem auszugebenden Text gefüllt.

Ein weiterer Vorteil dieser Methode ist die Möglichkeit den Ausgabetext beliebig mit CSS zu gestalten.

Dies war die früher übliche Methode, mit JavaScript auf einzelne Elemente zuzugreifen. Da man nicht genau wusste, welche Elemente später als Zugriffspunkte benötigt würden, wurden sicherheitshalber viele id-Attribute vergeben, die das HTML-Markup(womöglich noch in Verbindung mit präsentationsbezogenen Klassennamen ) unnötig aufblähten.

Empfehlung: Versuchen Sie semantisches Markup zu verwenden.
  • Verwenden Sie die passenden Elemente und nutzen Sie deren browsereigene Funktionalität!
  • Setzen Sie ids und Klassennamen nur sparsam ein.

Da diese Art des Elementzugriffs eigentlich umständlich war, entwickelten Frameworks wie jQuery eine Methode, die den direkten Zugriff auf Elemente über ihrem Tag-Namen oder über Klassen und Attribute ermöglichte. Diese querySelector-Methode wurde in natives JavaScript übernommen und wird von allen Browsern (selbst dem fast ausgestorbenen IE8) verstanden.

Im Folgenden verwenden wir die vielseitigere querySelector-Methode:

Beispiel ansehen …
<output></output>

<script>
  'use strict';
  var text = 'Hallo Welt!';
  
  document.querySelector('output').innerText = text;
  
</script>
Das (noch) leere output-Element wird über die querySelector-Methode angesprochen und mit innerText mit dem auszugebenden Text gefüllt.
Empfehlung: Gegenüber document.getElementById(), das nur Elemente anspricht, die ein eindeutiges, unverwechselbares id-Universalattribut besitzen, sucht die querySelector-Methode nach den im CSS üblichen Selektoren wie Elementen, Klassen und IDs, aber auch ARIA-Attributen, wie wir später sehen werden.
Sie spricht das erste gefundene Element an. Mit QuerySelectorAll könnten Sie alle vorhandenen Vorkommen ansprechen.


Mit diesen Methoden können Sie auf das DOM (und dessen Elementknoten) einer Webseite zugreifen:

  • querySelector: gibt das erste Element zurück, das dem angegebenen CSS-Selektor entspricht
  • querySelectorAll: gibt eine Liste von Elementen zurück, die dem angegebenen CSS-Selektor entsprechen
  • getElementsByClassName: gibt eine Liste von Elementen zurück, die der angegebenen Klasse entsprechen
  • getElementById: gibt das Element zurück, das die angegebene id besitzt.
  • getElementsByTagName: greift auf ein beliebiges Element im Elementenbaum des Dokuments zu.

Textknoten ansprechen[Bearbeiten]

Die meisten Elemente wie Überschriften, Absätze, Links und Buttons enthalten neben möglichen Kind-Elementen noch Textknoten mit dem eigentlichen Inhalt der Webseite.

Im oberen Beispiel wurde dieser Textknoten mit innerText verändert:

Beispiel: innerText
  document.getElementById('info').innerText = text;

Diese Eigenschaft wurde von Microsoft für den Internet Explorer entwickelt, von Firefox aber erst mit der Version 45 im März 2016 übernommen.

Deshalb findet sich in vielen älteren Beispielen eine etwas umständlichere Methode:

Beispiel: firstChild.nodeValue
  document.getElementById('info').firstChild.nodeValue = text;
In diesem Beispiel wird mit Node.firstChild der erste Kindknoten des Elements mit der id info ermittelt und dann dessen Wert mit Node.nodeValue geändert.

Des Weiteren existiert mit innerHTML eine weitere Methode, mit der Sie nicht nur Text, sondern sogar weitere HTML-Kindelemente einfügen können.

Beispiel: innerHTML
  document.getElementById('info').innerHTML = '<strong>' + text + '</strong>';
In diesem Beispiel wird die Variable text von zwei Zeichenketten umschlossen, in denen sich das Markup für ein strong-Element befindet. Wenn der Code ausgeführt wird, ändert sich nicht nur der Textknoten, sondern es wird ein strong-Element in den Elementbaum eingefügt.
Beachten Sie: Durch die Verwendung von innerHTML könnten hier auch weitere HTML-Elemente beinhaltet sein. Dies wird jedoch als unsicher angesehen, da so unter Umständen Benutzereingaben mit schädlichen Skripten in die Webesite eingefügt werden können.

Ereignisverarbeitung[Bearbeiten]

Alle bisherigen Beispiele wurden direkt mit dem Laden des Script ausgeführt. Dies hat aber (noch) nichts mit Interaktivität zu tun. Die Event-API ermöglicht die Überwachung und Behandlung von Ereignissen (Event-Handling). Dies können Klicks mit der Maus, Wischgesten (Swipes) auf einem Touchbildschirm, ankommende Daten aus dem Netzwerk oder Änderungen am DOM sein.

Moderne Scripte durchlaufen deshalb verschiedene Phasen:

  1. Das Dokument wird geladen
  2. JavaScript fügt Event-Handler an Elementknoten an.
  3. Der Anwender bedient das Dokument und das Script reagiert darauf.


window.onload vs. DOMContentLoaded[Bearbeiten]

In diesem Beispiel ist der JavaScript-Code im head des Dokuments gespeichert. Der Zugriff auf das output-Element würde erfolgen, bevor der Absatz im DOM überhaupt vorhanden wäre.

Deshalb wird das Script mit window.onload erst ausgeführt, wenn die Seite im Browser geladen und geparst ist.

Beispiel ansehen …
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <script>
    'use strict';
    function init(){
      var text = 'Hallo Welt!'; 
      document.querySelector('output').innerText = text;
    }

    window.onload = init;  
  </script>  
  <title>DOM-Tutorial-3</title>
</head>

<body>
  <h1>DOM-Tutorial Beispiel 3</h1>
  
  <output></output>
</body>
</html>
Das Script ist nun im head der Webseite eingebunden.

Der bisherige JavaScript-Code ist nun in eine Funktion mit dem Namen init() integriert.
Sobald die Seite vollständig geladen ist, wird das load-Ereignis gefeuert und die Funktion init aufgerufen.

Diese führt unsere Textausgabe aus.
Beachten Sie, dass bei dieser Notation ein evtl. schon vorhandener Eventhandler überschrieben wird.

Deshalb sollte die Methode addEventListener verwendet werden, um

  • evtl. schon vorhandene Event-Handler von anderen Programmierern zu berücksichtigen
  • mögliche Erweiterungen nicht von vornherein auszuschließen.


Ein weiterer Nachteil ist, dass das vollständige Laden der Webseite auch das Laden aller externen Ressourcen wie Bilder, Videos und weitere Scripte einschließt. Glücklicherweise gibt es ein Ereignis, das eintritt, sobald der Parser den gesamten HTML-Code eingelesen hat und der komplette DOM-Baum für JavaScripte zugänglich ist: DOMContentLoaded.

Beispiel ansehen …
'use strict';
document.addEventListener('DOMContentLoaded', function () {
 
  var text = 'Hallo Welt!'; 
  document.querySelector('output').innerText = text;
 
});
An das Document-Objekt als Ausgangspunkt des Elementbaums wird mit der addEventListener-Methode ein Event-Handler angehängt.
Wenn das DOM aufgebaut ist, feuert das DOMContentLoaded-Ereignis und ruft eine anonyme Funktion auf, die unser JavaScript-Script enthält.
Diese Funktion ist eine IIFE ( Immediately-invoked Function Expression), ein sofort ausgeführter Funktionsausdruck und hat den Vorteil, dass alle Variablen und lokalen Funktionen in einem privaten Raum ausgeführt werden, ohne evtl. andere Skripte zu beeinflussen.
Empfehlung:

Verwenden Sie so oft wie möglich addEventListener gegenüber dem Hinzufügen von Eventhandlern mit der Punktnotation.

Vermeiden Sie auf jeden Fall das Hinzufügen von Eventhandlern mit HTML-Event-Attributen, wie es in älteren Scripten noch zu sehen ist. Auch wenn es auf den ersten Blick schneller und einfacher scheint, verstößt man so gegen die Trennung von Inhalt und Verhalten und selbst bei kleineren Projekten wird der Code schnell unübersichtlich, wie Sie in diesem Beispiel aus den Jahr 2000 sehen können.

auf Klicks reagieren[Bearbeiten]

Bis jetzt wurde unser Programm automatisch immer nach dem Laden der Webseite ausgeführt. Interessant wird es jedoch erst, wenn der Anwender selbständig eingreifen kann und unser Programm reagiert.

Beispiel ansehen …
  <main>
     <button id="button">Drück mich!</button>
     <output></output>
  </main>
In diesem Beispiel sehen Sie einen Button (noch ohne Funktion) und das noch leere output-Element.
Das JavaScript befindet sich in einem script-Element im Head des Dokuments:
document.addEventListener('DOMContentLoaded', function () {
 
  var anzahl = 0;
  var text ='';
 
  document.getElementById('button').addEventListener('click', gedrueckt);
 
  function gedrueckt() {
    anzahl = anzahl + 1; 
 
    if (anzahl < 10) {
      text = 'Sie haben den Button ' + anzahl +'mal gedrückt!';
    }
    else if (anzahl < 15) {
      text = 'Sie haben jetzt bereits ' + anzahl + 'mal geklickt.\n' +
             'Wollen Sie sich nicht einen Kaffee gönnen?';
    }
    else {
      text = 'Jetzt haben Sie schon ' + anzahl + 'mal geklickt - mir wird langweilig.';
    }
    document.querySelector('output').innerText = text;
  }
 
});
Wenn das DOM aufgebaut ist, feuert DOMContentLoaded und führt die anonyme Funktion aus.

Mit der addEventListener-Methode wird eine Ereignisüberwachung an den Button gehängt.

Sobald der Button gedrückt (click) wird, wird die Funktion gedrueckt aufgerufen. Diese erhöht den Zähler um 1 und gibt dann aus, wie oft der Button bereits angeklickt wurde.

Die Ereignisüberwachung anderer Events wie mousemove oder touchstart (dem Berühren eines Touchscreens) kann genauso eingerichtet werden.

DOM-Manipulation[Bearbeiten]

Wenn ein Anwender mit der Webseite interagiert, ist es oft nötig, je nach erfolgter Eingabe unterschiedliche Inhalte zur Verfügung zu stellen. So könnte bei einem Login für neue Nutzer ein Anmeldeformular eingeblendet werden. Wenn der Benutzer schon registriert ist, kann er nach dem Einloggen einfach weitermachen, für neue Benutzer werden je nach Stand der Eingabe unterschiedliche Inhalte dynamisch nachgeladen.

Elemente dynamisch erzeugen[Bearbeiten]

Gerade bei Spielen wird das Spielfeld oft erst geladen, wenn der Benutzer die Spielanleitung oder den bisherigen Punktestand (Highscore) gelesen hat, da das Hin- und Herklicken zwischen verschiedenen Webseiten zu umständlich wäre.

Hierfür können Sie Inhalte dynamisch ausgeben, indem Sie HTML-Elemente erzeugen, mit Inhalt füllen und in das DOM der Webseite einhängen:


Beispiel ansehen …
<button id="button">Drück mich!</button>
<div id="textblock">
   <p>Dies ist ein Textblock, dem dynamisch weitere Absätze angehängt werden können.</p>
</div>
Die Webseite enthält einen Button und ein div, in dem sich ein Textabsatz befindet.
document.addEventListener('DOMContentLoaded', function () {

  var text = 'Dies ist ein neuer Absatz'; 
 
  document.getElementById('button').addEventListener('click', createElement);

  function createElement(){
    var container = document.getElementById('textblock');
    var newElm = document.createElement('p');
    newElm.innerText = text;
    container.appendChild(newElm);
  }
 
});
Das Script ruft auf Klick die Funktion createElement() auf.

Dies erzeugt mit createElement ein neues p-Element und weist es der Variablen newElm zu. Dem Absatz wird mit innerText eine in der Variablen text enthaltenen Zeichenkette als Textknoten an das Element hinzugefügt. Anschließend wird das so dynamisch erzeugte Element mit appendChild in das DOM eingehängt.

Wenn Sie es mit der Konsole untersuchen, werden Sie keinen Unterschied zu normalen Textabsätzen finden.

eine Helferfunktion für neue Elemente[Bearbeiten]

Mit der oberen Funktion konnten neue Absätze erzeugt werden. Für den Alltagseinsatz benötigt man aber eine Helfer-Funktion, mit der beliebige neue Elemente an beliebigen Stellen im DOM erzeugt werden.

Beispiel ansehen …
document.addEventListener('DOMContentLoaded', function () {

  var text1 = 'Dies ist ein neuer Absatz!'; 
  var text2 = 'Dies ist ein neuer Button!';   

 
  document.getElementById('button1').addEventListener('click', function() {
    createElement('#textblock', 'p',text1);});
  document.getElementById('button2').addEventListener('click', function() {
    createElement('#textblock', 'button',text2);});	

  function createElement(parent,elem, content){
    var container = document.querySelector(parent);
    var newElm  = document.createElement(elem);
    newElm .innerText = content;
    container.appendChild(newElm );
  }
 
});
Die Funktion createElement() wurde jetzt so erweitert, dass ihr Parameter übergeben werden können.

Beim Anhängen der Event-Handler wird addEventListener als zweiter Parameter eine anonyme Funktion übergeben, in unsere Parameter gekapselt sind.

  • Der erste Parameter parent bestimmt, an welcher Stelle im Elementbaum das neue Element eingehängt wird.
  • elem legt fest, welches Element erzeugt wird.
  • content übergibt einen Inhalt, der dann als Textknoten eingefügt wird.

Ein Manko dieser Helferfunktion ist die fehlende Möglichkeit, weitere Klassen und Attribute festzulegen. So würde bei einem Bild oder einem Link ja zwingend eine URL benötigt.

Solche Helferfunktionen können Sie sich aber bei github kopieren oder selbst zusammenstellen.

Elemente dynamisch entfernen[Bearbeiten]

Sie können Elemente dynamisch erzeugen - sie aber auch dynamisch wieder entfernen. Dafür müssen Sie mit der parentNode-Eigenschaft den Elternknoten finden und dann dessen Kind (also das gewünschte Element) mit removeChild wieder entfernen.

Beispiel ansehen …
document.addEventListener('DOMContentLoaded', function () {
  document.addEventListener('click', removeElement);	
    
	
  function removeElement(e) {
    var elem = e.target;
    var main = document.querySelector('main');
    if (main != elem) {
      var parent = elem.parentNode;
      parent.removeChild( elem);
      return false;
    }
  }

});
In diesem Beispiel können Sie einzelne Elemente aus der Webseite löschen. Dafür wird mit event.target ermittelt, in welchem Element das Ereignis aufgetreten ist und dieser Wert der Variable elem zugewiesen.

Damit nicht die gesamte Seite gelöscht wird, überprüft man in einer bedingten Anweisung, ob das geklickte Ereignis im main-Element auftrat.

Bei allen anderen Elementen wird mit parentNode der Elternknoten ermittelt und dann mit removeChild(elem) das angeklickte Element entfernt.

Ein kleines Browserspiel[Bearbeiten]

Am Beispiel eines kleinen Zahlenspiels können Sie die Möglichkeiten der DOM-Manipulation nun Schritt für Schritt nachvollziehen.

Spielidee[Bearbeiten]

  1. Eine Zufallszahl wird als Additionsziel definiert und angezeigt.
  2. In einem Feld aus Zahlen sollen solange Zahlen angeklickt werden, bis dieses Ziel erreicht wird.
    1. Auf jeden Klick hin wird insgeheim eine Zwischensumme mit dem Wert der Klickzahl verändert.
      (Die Klick-Zahl selbst bleibt unverändert.)
    2. Wenn die Zufallszahl erreicht oder überschritten wird, wird eine Erfolgs- oder Misserfolgsmeldung ausgegeben
    3. Mit dem Erreichen oder Über-/Unterschreiten der Zufallszahl wird die geheime Zwischensumme auf 0 gesetzt und eine neue Zufallszahl definiert und angezeigt.
  3. Das Spiel endet, wenn …
    1. wenn alle Zahlen angeklickt worden sind
    2. wenn die restlichen Klick-Zahlen die geforderte Zufallszahl nicht mehr erreichen können.

Das Spiel wird einzelne Funktionen aufgeteilt:

  1. eine Funktion createNumbers(), die die Arrays mit den (Zufalls)-Zahlen erzeugt
  2. eine Funktion createGameBoard(), die die Buttons mit den enthaltenen Zahlen erzeugt und next() aufruft
  3. eine Funktion next(), die
    1. die Zwischensumme resettet
    2. eine neue Zufallszahl festlegt.
    3. Dann muss sie prüfen, ob die restlichen Klick-Zahlen die neue Zufallszahl überhaupt erreichen können, ansonsten ruft sie end() auf.
  4. eine Funktion click(), die dafür sorgt, dass …
    1. die Klick-Zahl verrechnet wird
    2. das geklickte Element visuell verschwindet.
    3. Im Anschluss bewertet sie, ob die Zufallszahl erreicht/überschritten wurde und aktualisiert eventuell die Anzeige. Nach einer kurzen Verzögerung ruft sie nötigenfalls next() auf.
  5. Eine Funktion end(), die bei Spielende das Resultat ausgibt und evtl. einen Neustart anbietet

Zufallszahlen erzeugen und zerlegen[Bearbeiten]

Im Spiel sollen Sie solange Zahlen addieren, bis die gegebene Summe erreicht ist. Dafür benötigen wir eine Reihe von Summen (numbers) und dementsprechend viele Teiler (factors). Mit einigen Funktionen können Sie sie schnell erzeugen:

Beispiel: Zufallszahlen erzeugen und zerlegen ansehen …
'use strict';
document.addEventListener('DOMContentLoaded', function () {

var size  = 1,       //Anzahl der Zahlen des Arrays kann beliebig verändert werden
    numbers  = [],   //Array der Additionsziele
    factors = [];    //Array der Teiler der Additionsziele 

document.getElementById('button').addEventListener('click', createGame);
		
function createGame(){		
  for (var i = 0; i < size; i++) {
    numbers[i] = (rand(10 , 19));
    var zahl = (numbers[i]),         // Zerlegt die Zufallszahl in drei Teiler (Faktoren)
        factor1 = rand(3,(zahl/2)),
        factor2 = rand(1,4);
    factors[3 * i + 0] = factor1;
    factors[3 * i + 1] = factor2;
    factors[3 * i + 2] = zahl - (factor1 + factor2);	
  }
  //Ausgabe
  createElement('#textblock', 'p', numbers);
  createElement('#textblock', 'p', factors);
}

// erzeugt Zufallszahlen
function rand (min, max) {			
  return Math.floor(Math.random() * (max - min + 1)) + min;
} 

function createElement(parent, elem, content){
...
}
 
});
Zu Beginn werden drei Variablen festgelegt. Ein Array numbers mit unseren zufällig erzeugten Additionszielen; die Größe des Arrays wird durch size festgelegt und ein weiterer Array factors mit den Teilern.

Die Funktion createGame füllt den Array numbers. In einer Schleife wird das Array mit Zufallszahlen gefüllt, die mit der Helferfunktion rand (min, max) erzeugt werden.
Anschließend werden die einzelnen Werte des Arrays in 3 Teiler zerlegt und im Array factors hinterlegt.

Mithilfe der oben besprochenen Helferfunktion createElement() werden die beiden Arrays in je einem Textabsatz ausgegeben.

Spielfeld[Bearbeiten]

Nachdem die Überprüfung erfolgreich war, können Sie nun das Spielfeld anlegen:

Beispiel: Anlegen des Spielfelds ansehen …
function createGame(){
	
  var container = document.querySelector('#game');
 
  if (container) {			//löscht evtl. vorhandene Elemente
    while (container.firstChild) {
      container.removeChild(container.firstChild);
    }
  }	
	
  for (var i = 0; i < size; i++) {
    numbers[i] = (rand(10 , 19));
    var zahl = (numbers[i]),         // zerlegt die Zufallszahl in drei Teiler (Faktoren)
        factor1 = rand(3,(zahl/2)),
        factor2 = rand(1,4);
    factors[3 * i + 0] = factor1;
    factors[3 * i + 1] = factor2;
    factors[3 * i + 2] = zahl - (factor1 + factor2);	
  }
  
  shuffle(factors);
  
  //Ausgabe
    for (var i = 0; i < factors.length; i++) {	//erzeugt in einer Schleife neue Elemente
    createElement('#game', 'button', factors[i]);
  }  
  var x = document.querySelectorAll('button'),  //Buttons erhalten eine Klasse für eine zufällige Färbung
      i;
  for (i = 0; i < x.length; i++) {
    x[i].className = 'type' + rand(1,9);
  }
  
  createElement('#game', 'output', 'Zahl');
  document.querySelector('output').id = 'display';
}
Die Funktion createGame() löscht zunächst alle evtl. noch vorhandenen Elemente. Hiermit kann verhindert werden, dass noch bestehende Inhalte bzw. Event-Handler auf das aktuelle Spielfeld wirken.

In der for-Schleife werden , wie oben besprochen, die Zufallszahlen erzeugt und in Teilsummen (factors) zerlegt.
Danach wird in einer Schleife für jeden Wert des Arrays factors ein Button erzeugt. Der oben besprochenen HelferFunktion createElement('#game', 'button', factors[i]); werden die id des Elternelements, der gewünschte Elementname und der Inhalt des zu überzeugenden Elements übergeben. Mit dem dritten Parameter factors[i] werden die entsprechenden Werte des Arrays zugewiesen.

Anschließend wird ein output-Element erzeugt, das die Zufallszahl beinhalten soll; es wird mit querySelector angesprochen und erhält eine id display.
Beispiel: DOM vor und nach der Initialisierung ansehen …
  <div id="game">
    <p>Für dieses Spiel benötigen Sie JavaScript.</p>
    <button id="button">Start!</button>
  </div>
Das Spiel besteht aus einem div mit der id game, das einen Hinweis enthält, und einem Button.
Durch einen Klick auf diesen erhlaten Sie:
<div id="game">
  <button class="type9">2</button>
  <button class="type7">5</button>
  <button class="type1">4</button>
  <button class="type5">4</button>
  <button class="type3">1</button>
  <button class="type4">12</button>
  <button class="type5">5</button>
  ...
  <output id="display">Zahl</output>
</div>
Das Spiel besteht aus einem div mit der id game, das jetzt die Buttons mit den Teilsummen und ein output-Element für die Berechnungen und Ergebnisse enthält.

Spielfeld mit CSS gestalten[Bearbeiten]

Gut geht anders - die Buttons sehen aus wie Buttons. Deshalb sollte unser Spiel mit CSS gestylt werden. Das output-Element und das Spielbrett können über ihre Selektoren angesprochen werden. Die Buttons haben jedoch (noch) keine Unterscheidungsmerkmale.

Beispiel: Anlegen des Spielfelds ansehen …
function createOutput(parent,elem, content){

  ...

  var x = document.querySelectorAll('button'),  
      i;
  for (i = 0; i < x.length; i++) {
    x[i].className = 'type' + rand(1,12);
  }

  ...

  }
Die createGame()-Funktion wird leicht verändert. Über querySelectorAll werden alle neu erzeugten Buttons ausgewählt und erhalten dann mittels className eine nach dem Zufallsprinizp ausgewählte Klasse zugewiesen.
Beachten Sie: Mit einer komfortableren Helferfunktion, bei der Sie auch optionale Attribute übergeben und erzeugen können, wäre diese Schleife nicht nötig, da dann nur z.B. class : 'type' + rand(1,12); als zusätzlicher Parameter übergeben werden müsste.

Da Sie den Buttons Klassen zugewiesen haben, können Sie sie jetzt mit CSS unterschiedlich gestalten:

Beispiel: Formatierung mit CSS ansehen …
output {
  display: block;
  float: right;
  width: 8rem;
  height:8rem;
  background: #333;
  font-size: 4em; 
  text-align: center;
  border: 1px solid #000066;
  border-radius:10%;
}

button[class^="type"]  {
  background-color: #FFFFFF;
  border: 1px solid #000066;
  border-radius: 40%;
  font-size: 120%;
  font-weight: bold;
  margin: 0 5px 5px 0;
  padding: 0.25em 0;
  text-align: center;
  width: 3rem; 
  height: 3rem;
}
button:focus,button:hover {
  border: 2px solid yellow;
  color: yellow;
}

button.type1 {background: white; }
button.type2 {background: yellow;}
button.type3 {background: beige;}
button.type4 {background: orange;}
Alle Buttons, die den String type im Klassennamen enthalten, erhalten ein quadratisches Aussehen und werden größer als die Standardschriftgröße dargestellt.

Per Zufallsprinzip wurden den Buttons Klassen zugewiesen, die unterschiedliche Hintergrundfarben haben.

Die zu erreichende Zahl wird in output mit einer Schriftgröße von 4em angezeigt.
Empfehlung: Achten Sie bei der Festlegung der CSS-Eigenschaften auch auf eine klare Kennzeichnung von mit der Tastatur oder Maus ausgewählten Elementen mittels der Pseudoklassen :focus und :hover

Event-Delegation[Bearbeiten]

Das Spielfeld ist fertig - jetzt können wir uns um das Spiel selbst kümmern: die einzelnen Buttons sollen anklickbar gemacht werden. Was liegt näher als den Buttons beim Erzeugen ein click-Event zuzuweisen?

Beispiel: Klickfunktionalität im herkömmlichen Event-Handling
  for (i = 0; i < factors.length; i++) {
    newElm = document.createElement('button');
    container.appendChild(newElm);
    newElm.appendChild(document.createTextNode(factors[i]));
    newElm.onclick = clickHandler;
  }
Auch wenn das Hinzufügen mithilfe der Punkt-Notation simpel aussieht, birgt es doch ein großes Problem: Da jeder einzelne Button einen Event-Handler hat, gibt es insgesamt 36 einzelne davon.


Die Überwachung zahlreicher Elemente im Dokument ist sehr aufwändig umzusetzen und langsam in der Ausführung, da jedes Element herausgesucht, durchlaufen und bei jedem denselben Event-Handler registriert werden muss.

Bei solchen Aufgabenstellungen können Sie vom Bubbling-Effekt profitieren, das ist das Aufsteigen der Ereignisse im DOM-Baum. Machen Sie sich die Verschachtelung der Elemente im DOM-Baum zunutze und überwachen Sie die Ereignisse von verschiedenen Elementen bei einem gemeinsamen, höherliegenden Element, zu dem die Ereignisse aufsteigen. Diese Technik nennt sich Event-Delegation (englisch delegation für Übertragung von Aufgaben). Dabei wird einem zentralen Element die Aufgabe übertragen, die Ereignisse zu verarbeiten, die bei seinen Nachfahrenelementen passieren.

Beispiel ansehen …
  function createGame(){
    ...
    container.addEventListener('click', click);	
    ...
  } 
}

 function click (e) { 
    var target = e.target;
    var value = target.innerText;
    alert (value + ' wurde geklickt!');
    removeElement(e);
  }
Vorteilhafter ist das dynamische Anbinden des Klick-Events an den Container mittels addEventListener.

Die aufgerufene Funktion click(e) ermittelt mit der Eigenschaft target von welchem Element das Ereignis ausgeht.

Anschließend wird das geklickte Element mit der Helferfunktion removeElement() entfernt.

Das Spiel läuft[Bearbeiten]

Mit jedem Klick auf einen Button wird nun überprüft, ob das Additionsziel erreicht wurde; Falls ja wird die Funktion next() aufgerufen, die ein weiteres Additionsziel ausgibt. Gibt es kein Additionsziel oder verfügbare Buttons mehr, wird die Funktion end() aufgerufen;

Beispiel ansehen …
  function createGame(){
    ...
    container.addEventListener('click', click);	
    ...
  } 
}

 function click (e) { 
    var target = e.target;
    var value = target.innerText;
    alert (value + ' wurde geklickt!');
  }

DOM-Manipulation und CSS[Bearbeiten]

Mit JavaScript können Sie das DOM so manipulieren, dass das Aussehen der Seite völlig verändert wird. Dabei sollten aber vorzugsweise die Elemente nicht mit style formatiert werden, sondern bereits im Stylesheet vorhandene Klassenattribute gesetzt, geändert und entfernt werden. Existiert für diese Klasse eine Festlegung im Stylesheet, wird die Darstellung vom Browser sofort geändert.

Klassenattribute hinzufügen und entfernen[Bearbeiten]

Beim Erreichen oder Überschreiten des Additionsziels soll eine Erfolgs- bzw. Misserfolgsmeldung angezeigt werden. Dabei machen wir uns zunutze, dass ein Setzen von Klassen nicht nur das Aussehen des selektierten Elements verändern kann, sondern auch Pseudoklassen wie ::after und ::before.

Beispiel: auf das CSS wirken ansehen …
.exact {
	font-weight: bold;
	color: lime;
}

.exact:after {
	content: "✓";
	display: block;
	margin: -0.5em 0 0 1.5em;
}

.inexact {
	font-weight: bold;
	color:red;
}

.inexact:after {
	content: "✗";
	display: block;
	margin: -0.5em 0 0 1.5em;
}
if (current <= 0) {

  diff.push(current);

  display.className = (
	current === 0
	? "exact"
	: "inexact"
  );
setTimeout(next, 1500);
				}
In einer bedingten Abfrage wird überprüft, ob current kleiner gleich 0 ist. Falls dies zutrifft, wird überprüft, ob der Wert von current 0 ist und mit einem ternären Operator für den true-Fall dem output-Element mit className die Klasse exact, für den false-Fall die Klasse inexact zugewiesen.
Beachten Sie: Da der Klassenname keine Variable ist, muss er in Anführungszeichen gesetzt werden.

Die geänderte Klasse und die Erfolgs- bzw. Fehlermeldungen bleiben für 1500ms sichtbar, da der Aufruf eines neuen Durchlaufs mit setTimeout verzögert wurde.

In der Funktion next() werden evtl. vorhandene Klassenattribute durch className = ''; wieder gelöscht.

Die hier vorgestellte Vorgehensweise ermöglicht es, nicht nur die Textfarbe, sondern eine Vielzahl von CSS-Eigenschaften gleichzeitig zu ändern.

Nachteilig an der Verwendung der className-Eigenschaft ist die Überschreibung eines eventuell schon vorher vorhandenen Klassenattributs. Mit der classList-Eigenschaft können Sie komfortabel eine (auch von mehreren) Klassen ändern, hinzufügen oder löschen.

das fertige Spiel[Bearbeiten]

In der vorherigen Variante verändert sich die Position der Buttons mit jedem Klick, da der Platz der durch Klicks entfernten Buttons von seinem Nachbarn eingenommen wird.

Schöner wäre es, wenn die Buttons nur in einer Richtung (Luftblasen vergleichbar) nach oben steigen würden.


Beispiel: auf das CSS wirken ansehen …
function createGameBoard(arr) {	
  var i, 
      parent = document.querySelector('#gameboard');
 parent.addEventListener('click', clickHandler);
 
  if (parent) {			//löscht evtl. vorhandene Elemente
    while (parent.firstChild) {
      parent.removeChild(container.firstChild);
    }
  }	
 
  for (i = 0; i < arr.length; i++) {	//erzeugt in einer Schleife neue Elemente
    if ((i+6) % 6 == 0){				//Abfrage, ob Zahl durch 6 teilbar ist, erzeugt dann Spalte
	  var div = document.createElement('div');  
      parent.appendChild(div);
	}
	createOutput('#gameboard', 'button', arr[i]);
  } 
}
In der Funktion createGameBoard() werden nun 2 Zeilen eingefügt, so dass immer je sechs Buttons innerhalb eines div angelegt werden.

Weblinks[Bearbeiten]