Das dynamische Verhalten moderner Webanwendungen basiert auf der Fähigkeit, das DOM zu manipulieren und auf Benutzerereignisse zu reagieren. Dieser Abschnitt behandelt die grundlegenden und fortgeschrittenen Techniken zur DOM-Manipulation sowie das Event-System in JavaScript.
Die Manipulation des DOM lässt sich in mehrere Kategorien unterteilen: Elemente erstellen, modifizieren, einfügen und entfernen.
// Beispiel: Neue DOM-Elemente erstellen
const paragraph = document.createElement('p');
const textNode = document.createTextNode('Dieser Text wurde dynamisch erstellt.');
paragraph.appendChild(textNode);
// Alternativ direktes Setzen von innerHTML
paragraph.innerHTML = 'Dieser Text wurde <strong>dynamisch</strong> erstellt.';// Beispiel: Setzen und Modifizieren von Attributen
const link = document.createElement('a');
// Klassische Methoden
link.setAttribute('href', 'https://example.com');
link.setAttribute('target', '_blank');
const href = link.getAttribute('href');
link.removeAttribute('target');
// Direkter Zugriff auf Eigenschaften
link.id = 'main-link';
link.className = 'external-link highlighted';
link.href = 'https://updated-example.com';
// Verwaltung von Klassen mit dem classList-API
link.classList.add('new-class');
link.classList.remove('highlighted');
link.classList.toggle('active'); // Fügt Klasse hinzu, wenn nicht vorhanden, sonst entfernt
link.classList.contains('external-link'); // Prüft, ob eine Klasse vorhanden ist// Beispiel: Einfügen von Elementen an verschiedenen Positionen
const container = document.querySelector('.container');
const existingChild = container.querySelector('.existing-child');
// Am Ende eines Elements einfügen
container.appendChild(paragraph);
// Vor einem Referenzelement einfügen
container.insertBefore(link, existingChild);
// Moderne Methoden mit flexiblerer Positionierung
container.append(paragraph, link); // Fügt mehrere Nodes am Ende ein
container.prepend(textNode); // Fügt am Anfang ein
existingChild.before(paragraph); // Fügt vor dem Element ein
existingChild.after(link); // Fügt nach dem Element ein
existingChild.replaceWith(newElement); // Ersetzt das Element// Beispiel: Entfernen von Elementen
// Klassische Methode
container.removeChild(link);
// Moderne Methode
paragraph.remove(); // Self-removalFür die Änderung von Inhalten existieren verschiedene Ansätze mit unterschiedlichen Performance- und Sicherheitsimplikationen:
// Beispiel: Modifikation von Inhalten
// innerHTML: Schnell, aber potentielles XSS-Risiko
element.innerHTML = '<span class="highlight">Neuer Inhalt</span>';
// textContent: Sicherer, behandelt Text als reinen Text
element.textContent = 'Neuer Text & <tags> werden escaped';
// innerText: Ähnlich wie textContent, aber berücksichtigt CSS-Sichtbarkeit
element.innerText = 'Dieser Text berücksichtigt Styling';
// Node-basierter Ansatz: Sicher und flexibel
element.innerHTML = ''; // Inhalte löschen
const span = document.createElement('span');
span.textContent = 'Neuer Inhalt';
span.className = 'highlight';
element.appendChild(span);DOM-Elemente können über verschiedene Ansätze gestylt werden:
// Beispiel: Styling über JavaScript
// Inline-Styles direkt setzen
element.style.color = 'blue';
element.style.backgroundColor = '#f0f0f0';
element.style.marginTop = '20px';
// Mehrere Styles auf einmal setzen
Object.assign(element.style, {
padding: '10px',
borderRadius: '4px',
boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
});
// CSSText für viele Eigenschaften
element.style.cssText = 'color: red; font-size: 16px; margin: 10px;';
// Klassen-basierter Ansatz (bevorzugt)
element.classList.add('highlighted', 'active');In der modernen Webentwicklung ist der klassenbasierte Ansatz meist zu bevorzugen, da er Trennung von Struktur und Präsentation ermöglicht und besser mit CSS-Frameworks und Responsiveness zusammenarbeitet.
JavaScript-Events sind das Fundament für interaktive Webanwendungen. Sie ermöglichen die Reaktion auf Benutzerinteraktionen und andere Aktionen.
// Beispiel: Event-Listener hinzufügen
const button = document.querySelector('button');
// Methode 1: addEventListener (bevorzugt)
button.addEventListener('click', function(event) {
console.log('Button wurde geklickt!', event);
});
// Methode 2: On-Property (limitiert auf einen Handler pro Event)
button.onclick = function(event) {
console.log('Button wurde geklickt!', event);
};
// Methode 3: Inline-Event-Handler (veraltet, nicht empfohlen)
// <button onclick="handleClick()">Klick mich</button>Das Event-Objekt enthält wertvolle Informationen über das ausgelöste Ereignis:
// Beispiel: Arbeiten mit dem Event-Objekt
button.addEventListener('click', function(event) {
// Allgemeine Event-Eigenschaften
console.log('Event-Typ:', event.type);
console.log('Event-Ziel:', event.target);
console.log('Aktuelle Target:', event.currentTarget);
console.log('Zeitstempel:', event.timeStamp);
// Maus-spezifische Eigenschaften bei Mausevents
console.log('Mausposition:', event.clientX, event.clientY);
console.log('Gedrückte Taste:', event.button);
console.log('Alt/Shift/Ctrl gedrückt:', event.altKey, event.shiftKey, event.ctrlKey);
// Standardverhalten verhindern
event.preventDefault();
// Bubbling stoppen
event.stopPropagation();
});Event-Delegation ist ein leistungsstarkes Muster zur effizienten Handhabung vieler ähnlicher Events:
// Beispiel: Event-Delegation
// Statt vieler einzelner Listener für jedes Listenelement...
document.querySelector('.list').addEventListener('click', function(event) {
// Prüfen, ob das geklickte Element oder ein Elternelement ein Listenelement ist
const listItem = event.target.closest('li');
if (listItem) {
console.log('Listenelement geklickt:', listItem.textContent);
// Mit data-* Attributen arbeiten
const itemId = listItem.dataset.id;
if (itemId) {
console.log('Item ID:', itemId);
}
}
});Die Event-Delegation bietet mehrere Vorteile: - Reduzierte Anzahl an Event-Listenern - Automatische Handhabung dynamisch hinzugefügter Elemente - Geringerer Speicherverbrauch - Sauberere Codestruktur
Events durchlaufen drei Phasen: 1. Capturing-Phase:
Event wandert vom document zum Zielelement 2.
Target-Phase: Event erreicht das Zielelement 3.
Bubbling-Phase: Event steigt vom Zielelement zurück zum
document auf
// Beispiel: Event-Phasen
// Listener für die Bubbling-Phase (Standard)
parent.addEventListener('click', e => console.log('Parent Bubbling'), false);
child.addEventListener('click', e => console.log('Child Bubbling'), false);
// Listener für die Capturing-Phase
parent.addEventListener('click', e => console.log('Parent Capturing'), true);
child.addEventListener('click', e => console.log('Child Capturing'), true);
// Bei Klick auf das Child-Element ist die Reihenfolge:
// 1. Parent Capturing
// 2. Child Capturing
// 3. Child Bubbling
// 4. Parent BubblingNeben den nativen Events können auch benutzerdefinierte Events erstellt und ausgelöst werden:
// Beispiel: Custom Events erstellen und auslösen
// Event erstellen
const productAddedEvent = new CustomEvent('productAdded', {
bubbles: true,
detail: { productId: 123, name: 'Beispielprodukt', price: 29.99 }
});
// Event auslösen
document.querySelector('.shopping-cart').dispatchEvent(productAddedEvent);
// Event abfangen
document.addEventListener('productAdded', function(event) {
console.log('Produkt hinzugefügt:', event.detail);
updateCartUI(event.detail);
});Custom Events eignen sich besonders für die modulare Entwicklung und Komponenten-Kommunikation in größeren Anwendungen.
Webapplikationen nutzen eine Vielzahl von Event-Typen:
// Beispiel: UI-Events
window.addEventListener('load', () => console.log('Seite vollständig geladen'));
window.addEventListener('DOMContentLoaded', () => console.log('DOM geladen, vor Bildern/Styles'));
window.addEventListener('resize', () => console.log('Fenstergröße geändert'));
window.addEventListener('scroll', () => console.log('Scrollposition geändert'));// Beispiel: Maus- und Touch-Events
element.addEventListener('click', handleClick);
element.addEventListener('dblclick', handleDoubleClick);
element.addEventListener('mousedown', handleMouseDown);
element.addEventListener('mouseup', handleMouseUp);
element.addEventListener('mousemove', handleMouseMove);
element.addEventListener('mouseover', handleMouseOver);
element.addEventListener('mouseout', handleMouseOut);
// Touch-Events
element.addEventListener('touchstart', handleTouchStart);
element.addEventListener('touchmove', handleTouchMove);
element.addEventListener('touchend', handleTouchEnd);// Beispiel: Tastatur-Events
document.addEventListener('keydown', function(event) {
console.log('Taste gedrückt:', event.key, 'Keycode:', event.keyCode);
// Tasten-Kombinationen erkennen
if (event.ctrlKey && event.key === 's') {
event.preventDefault(); // Browser-Speicherdialog verhindern
saveDocument();
}
});
document.addEventListener('keyup', handleKeyUp);
document.addEventListener('keypress', handleKeyPress); // Veraltet, besser keydown verwenden// Beispiel: Formular-Events
const form = document.querySelector('form');
form.addEventListener('submit', function(event) {
event.preventDefault(); // Verhindert Seiten-Neuladen
validateAndSubmit();
});
const input = document.querySelector('input[type="text"]');
input.addEventListener('input', handleInput); // Bei jeder Änderung
input.addEventListener('change', handleChange); // Bei Änderung und Verlassen des Feldes
input.addEventListener('focus', handleFocus); // Bei Fokussierung
input.addEventListener('blur', handleBlur); // Bei Fokusverlust// Beispiel: IntersectionObserver für lazy loading und Scrolleffekte
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const element = entry.target;
// Lazy Loading für Bilder
if (element.dataset.src) {
element.src = element.dataset.src;
element.removeAttribute('data-src');
observer.unobserve(element);
}
// Scroll-Animation aktivieren
element.classList.add('visible');
}
});
}, {
root: null, // Viewport als Referenz
rootMargin: '0px',
threshold: 0.1 // 10% des Elements muss sichtbar sein
});
// Elemente beobachten
document.querySelectorAll('.lazy-image, .animate-on-scroll').forEach(el => {
observer.observe(el);
});// Beispiel: MutationObserver für DOM-Änderungen überwachen
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
console.log('Knoten hinzugefügt oder entfernt');
console.log('Hinzugefügte Knoten:', mutation.addedNodes);
console.log('Entfernte Knoten:', mutation.removedNodes);
} else if (mutation.type === 'attributes') {
console.log(
`Attribut ${mutation.attributeName} von ${mutation.target.nodeName} geändert`
);
}
});
});
// Konfiguration der zu beobachtenden Änderungen
observer.observe(targetNode, {
attributes: true,
childList: true,
subtree: true,
attributeOldValue: true
});// Beispiel: ResizeObserver für Element-Größenänderungen
const resizeObserver = new ResizeObserver(entries => {
entries.forEach(entry => {
const { width, height } = entry.contentRect;
console.log(`Element Größe: ${width}px × ${height}px`);
// Responsives Verhalten ohne Media Queries
if (width < 600) {
entry.target.classList.add('compact');
} else {
entry.target.classList.remove('compact');
}
});
});
resizeObserver.observe(document.querySelector('.resizable-component'));Event-Handler können erhebliche Performance-Implikationen haben, besonders bei häufig auftretenden Events:
// Beispiel: Debouncing für Performance-Optimierung
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// Anwendungsbeispiel: Effizienter Resize-Handler
window.addEventListener('resize', debounce(function() {
updateLayout();
}, 200));
// Anwendungsbeispiel: Effizienter Scroll-Handler
window.addEventListener('scroll', debounce(function() {
checkScrollPosition();
}, 100));Die folgenden Techniken verbessern die Event-Performance:
{ passive: true }// Beispiel: Passive Event Listener
document.addEventListener('touchstart', handleTouchStart, { passive: true });