JavaScript/Tutorials/Grundlagen des DOM

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

In diesem Tutorial lernen Sie, 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.

Voraussetzung sind die Tutorials

Inhaltsverzeichnis

[Bearbeiten] Was ist das DOM?

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.

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

[Bearbeiten] Ansprechen eines Elements

Erinnern Sie sich noch an unser "Hallo Welt"-Beispiel? Der Nachteil einer Textausgabe mittels alert ist die Speicherbelastung des Browsers und mangelnde Gestaltungsmöglichkeit des Ausgabefensters.

Beispiel ansehen …
<p id="info"></p>
 
 
<script>
  'use strict';
  var text = 'Hallo Welt!';
  ausgabe(text);
 
  function ausgabe(text){
    var ausgabe = document.getElementById('info');
    ausgabe.innerHTML = text;
  }
</script>
Das Script ist nun in eine Webseite eingebunden. Teil dieser Webseite ist ein (noch) leerer Absatz mit der id info.

Anstelle der Ausgabe mit alert wird eine Funktion ausgabe(text) aufgerufen. Mit document.getElementById wird das Element mit der ID info angesprochen und mit innerHTML mit dem auszugebenden Text gefüllt.

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


Weitere Möglichkeiten auf Elemente zuzugreifen sind:

  • 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
  • getElementsByTagName: greift auf ein beliebiges Element im Elementenbaum des Dokuments zu.

[Bearbeiten] Ereignisverarbeitung

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.


[Bearbeiten] window.onload vs. DOMContentLoaded

In diesem Beispiel ist der JavaScript-Code im head des Dokuments gespeichert. Der Zugriff auf den Absatz mit der id info 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!';
      ausgabe(text);
 
      function ausgabe(text){
        var ausgabe = document.getElementById('info');
        ausgabe.innerHTML = text;
      }
    }
 
    window.onload = init;  
  </script>  
  <title>DOM-Tutorial-2</title>
</head>
 
<body>
  <h1>DOM-Tutorial Beispiel 2</h1>
 
  <p id="info"></p>
</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.
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';
function init(){
  var text = 'Hallo Welt!';
  ausgabe(text);
 
  function ausgabe(text){
    var ausgabe = document.getElementById('info');
    ausgabe.innerHTML = text;
  }
}
 
document.addEventListener('DOMContentLoaded', init);
Wenn das DOM aufgebaut ist, feuert DOMContentLoaded und führt das Script aus. Das Ereignis ist mit addEventListener-Methode an das Dokument hinzugefügt.
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.

[Bearbeiten] auf Klicks reagieren

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 …
'use strict';
  var anzahl = 0;
  var text ='';
 
  function klickDazu(){  
    var obj = document.getElementById('button');
    obj.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.';
    }
    ausgabe(text);
  }
 
  function ausgabe(text) {
    var ausgabe = document.getElementById('info');
    ausgabe.innerHTML = text;
  }
 
document.addEventListener('DOMContentLoaded', klickDazu);
In diesem Beispiel sehen Sie einen Button (noch ohne Funktion) und den noch leeren Absatz.

Wenn das DOM aufgebaut ist, feuert DOMContentLoaded und führt die Funktion klickDazu aus. Diese Funktion hängt mit der addEventListener-Methode eine Ereignisüberwachung an den Button.

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 angeclickt wurde.

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

[Bearbeiten] DOM-Manipulation

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.

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

[Bearbeiten] Zufallszahlen erzeugen und zerlegen

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

Beispiel: Zufallszahlen erzeugen und zerlegen ansehen …
'use strict';
 
var zahl1,
    zahl2,
    zahl3,
    anzZahlen  = 4,
    zahlsumme  = new Array(anzZahlen),
    zahlTeiler = new Array(anzZahlen*3);
 
function ausgabeZahlen() {
  erzeugeUnsereZahlen();
  ausgabe(zahlsumme);
  ausgabe(zahlTeiler);	
}
 
// erzeugt einen Array, der eine festgelegte (anzZahlen) Anzahl von Zahlen hat.  
function erzeugeUnsereZahlen(){		
  for (var i = 0; i < anzZahlen; i++) {
    zahlsumme[i] = (rand(10,19)).toString();
    zerlegung (zahlsumme[i]);
    zahlTeiler[3*i+0]= zahl1.toString();
    zahlTeiler[3*i+1]= zahl2.toString();
    zahlTeiler[3*i+2]= zahl3.toString();
  }
}
 
// erzeugt Zufallszahlen
function rand (min, max) {			
  return Math.floor(Math.random() * (max - min + 1)) + min;
} 
 
// zerlegt die Variable zahl in 3 kleine Zahlen 
function zerlegung (zahl){			
  zahl1 = rand(3,(zahl/2));
  zahl2 = rand(1,4);
  zahl3 = zahl - (zahl1 +zahl2);
}
Die Funktion erzeugeUnsereZahlen füllt einen Array zahlSumme. Die Größe des Arrays wird durch die Variable anzZahlen festgelegt.

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 durch die Funktion zerlegung in 3 Teiler zerlegt und im Array zahlTeiler hinterlegt.

[Bearbeiten] Elemente dynamisch erzeugen

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 …
function ausgabeZahlen() {
  erzeugeUnsereZahlen();
  ausgabe(zahlSumme);
  ausgabe(zahlTeiler);	
}
 
function ausgabe(text){
  var container = document.getElementById('textblock');
  var absatzNeu = document.createElement('p');
  absatzNeu.innerHTML = text;
  container.appendChild(absatzNeu);
}
 
document.addEventListener('click', ausgabeZahlen);
<div id="textblock">
  <p>Dies ist ein Textblock, dem dynamisch weitere Absätze angehängt werden können.</p>
</div>
Das Script ruft auf Klick die Funktion erzeugeUnsereZahlen auf. Diese erzeugt zwei Arrays zahlSumme und zahlTeiler.

Um zu überprüfen, ob diese Zahlen richtig erzeugt wurden, werden sie mit der Funktion ausgabe() ausgegeben. Dabei wird anders als in Beispiel 1 kein bestehendes Absatz-Element verwendet, sondern ein neues absatzNeu mit createElement erzeugt. Mit innerHTML wird der der Funktion übergebene Text 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.

[Bearbeiten] Spielfeld

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

Beispiel: Anlegen des Spielfelds ansehen …
function erzeugeSpielfeld(){
  erzeugeUnsereZahlen();
  erzeugeButtons(zahlTeiler);
  erzeugeTest(zahlSumme[durchlauf]);
}
 
// Aus Gründen der Übersichtlichkeit heißt das Array zahlTeiler hier nur noch arr
function erzeugeButtons(arr) {	
  var i, 
      newElm,
      container = document.getElementById('container');
 
  if (container) {			//löscht evtl. vorhandene Elemente
    while (container.firstChild) {
      container.removeChild(container.firstChild);
    }
  }	
 
  for (i = 0; i < arr.length; i++) {	//erzeugt in einer Schleife neue Elemente
    newElm = document.createElement('button');
    newElm.className = 'typ' + rand(1,9);
    container.appendChild(newElm);
    newElm.appendChild(document.createTextNode(arr[i]));
  } 
}
 
function erzeugeTest (zahl){
  ausgabe(zahl);
  durchlauf = durchlauf + 1;
  zwischenergebnis = zahl;
}
 
document.addEventListener('DOMContentLoaded', erzeugeSpielfeld);
<div id="game">
  <div id="container"></div>
  <div id="menu"></div>
</div>
Die Funktion erzeugeButtons 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.

Danach wird in einer Schleife für jeden Wert des übergebenen Arrays ein Button erzeugt. Dieser Button erhält mit className eine nach dem Zufallsprinizp ausgewählte Klasse zugewiesen.
Danach werden Buttons mit arr[i] die entsprechenden Werte des Arrays zugewiesen. Dabei verzichten wir auf die Verwendung von innerHTML, da das Erzeugen eines Textknotens mit createTextNode schneller und performanter ist.
Anschließend wird der so erzeugte Button mit appendChild in den Elementenbaum eingehängt.

Die Funktion erzeugeTest gibt die Variable zahl im Absatz #menu p aus. Anschließend erhöht sie die Zählvariable durchlauf, damit beim nächsten Durchlauf eine weitere Zahl aus dem array geholt wird.


Gut geht anders - die Buttons sehen aus wie Buttons. Da Sie den Buttons Klassen zugewiesen haben, können Sie das Spielfeld mit CSS stylen:

Beispiel: Formatierung mit CSS ansehen …
button {
    background-color: #FFFFFF;
    border: 1px solid #000066;
    border-radius: 40%;
    color: #000000;
    float: left;
    font-size: 120%;
    font-weight: bold;
    margin: 0 5px 5px 0;
    padding: 0.25em 0;
    text-align: center;
    width: 50px; height:50px;
}
 
button.typ1 {background: red; }
button.typ2 {background: orange;}
button.typ3 {background: yellow;}
button.typ4 {background: lime;}
button.typ5 {background: skyblue;}
button.typ6 {background: blue; color: white;}
button.typ7 {background: purple; color: white}
button.typ8 {background: maroon; color: white;}
button.typ9 {background: darkgreen; color: white;}
 
#menu {
  float: right;
  width: 120px;
  background: blue;
}
 
#menu p {
  font-size: 60px; 
  text-align: center;
}
Alle Buttons erhalten ein quadratisches Aussehen und werden größer als die Standardschriftgröße dargestellt.

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

Die zu erreichende Zahl unsereZahl wird in #menu p mit einer Schriftgröße von 60px angezeigt.

[Bearbeiten] Event-Delegation

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
function erzeugeButtons(arr) {
 
  for (i = 0; i < arr.length; i++) {
    newElm = document.createElement('button');
    container.appendChild(newElm);
    newElm.appendChild(document.createTextNode(arr[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 erzeugeButtons(arr) {			
  var i, 
      newElm,
      container = document.getElementById('container');
  container.addEventListener('click', clickHandler);
 
  if (container) {			//löscht evtl. vorhandene Elemente
    while (container.firstChild) {
      container.removeChild(container.firstChild);
    }
  }	
 
  for (i = 0; i < arr.length; i++) {	//erzeugt in einer Schleife neue Elemente
    newElm = document.createElement('button');
    newElm.className = 'typ' + rand(1,9);
    container.appendChild(newElm);
    newElm.appendChild(document.createTextNode(arr[i]));
  } 
}
 
function clickHandler(e) {
	var target = e.target;
	geklickteZahl = target.innerHTML;
	zwischenergebnis = zwischenergebnis - geklickteZahl;
 
	if (zwischenergebnis == 0) {
		alert('Gewonnen!');
		erzeugeTest(zahlSumme[durchlauf]);
	}
	if (zwischenergebnis < 0){
		alert('Verloren!');
		erzeugeTest(zahlSumme[durchlauf]);
	}
	entferneElement(target);
}
Vorteilhafter ist das dynamische Anbinden des Klick-Events an den Container mittels addEventListener.

Die aufgerufene Funktion clickHandler(e) ermittelt mit der Eigenschaft target von welchem Element das Ereignis ausgeht.
Über die Variable zwischenergebnis wird berechnet, ob die gewünschte Summe erreicht oder sogar überschritten wird. In diesem Fall wird eine Meldung ausgegeben und ein neues Spiel erzeugt.

Anschließend wird das geklickte Element entfernt.

[Bearbeiten] Elemente dynamisch entfernen

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 …
    function klickFunktionalität (){
      var elem = document.getElementById('main');
      elem.addEventListener('click', elementEntfernen);	
    }
 
    function elementEntfernen(e) {
      var elem = e.target;
	  var main = document.getElementById('main');
	  if (main != elem) {
        var parent = elem.parentNode;
        parent.removeChild(elem);
        return false;
	  }
    }
 
    document.addEventListener('DOMContentLoaded', klickFunktionalität);
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.

[Bearbeiten] DOM-Manipulation und CSS

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.

[Bearbeiten] Klassenattribute hinzufügen und entfernen

Bis jetzt wurde der Spielstand über ein alert ausgegeben. Allerdings wäre eine Ausgabe anstelle der zu erreichenden Summe eleganter. Dabei soll jedoch auch die Darstellung geändert werden.

Beispiel: auf das CSS wirken ansehen …
.fehler {
  font-weight: bold; 
  color:red;
}
 
.erfolg {
  font-weight: bold; 
  color: lime;
}
function clickHandler(e) {
...
 
	if (zwischenergebnis == 0) {
		var absatz = document.getElementById('info');
		absatz.className = 'erfolg';
		ausgabe('✓ ');
		setTimeout('erzeugeTest(zahlSumme[durchlauf])', 1000);
	}
	if (zwischenergebnis < 0){
		absatz = document.getElementById('info');
		absatz.className = 'fehler';
		ausgabe('✗');
		setTimeout('erzeugeTest(zahlSumme[durchlauf])', 1000);
	}
...
}
 
function erzeugeTest (zahl){
  var absatz = document.getElementById('info');
  absatz.className = '';
  ausgabe(zahl);
  durchlauf = durchlauf + 1;
  zwischenergebnis = zahl;
}
In einer bedingten Abfrage wird überprüft, ob das Zwischenergebnis 0 entspricht. Dabei wird der Vergleichsoperator == verwendet, ein Gleichheitszeichen wäre ja eine Wertzuweisung, bei der der Wert 0 der Variable zwischenstand zugewiesen würde.

Innerhalb der bedingten Abfrage wird der Absatz mit der id info mittels getElementById identifiziert und der Variable absatz zugewiesen.
Mit className erhält das Element entweder die Klasse erfolg oder fehler .

Beachten Sie: Da dies keine Variable ist, muss sie in Anführungszeichen gesetzt werden.

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

In der Funktion erzeugeTest() 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.

[Bearbeiten] Weblinks



Meine Werkzeuge
Namensräume

Varianten
Aktionen
Übersicht
Index
Mitmachen
Werkzeuge
Spenden
SELFHTML