JavaScript Fortgeschritten

Event Handling in JavaScript

Event Handling in JavaScript

Events machen Webseiten interaktiv. Jeder Klick, jede Tastatureingabe, jedes Scrollen - alles sind Events, auf die du reagieren kannst.

Events verstehen

Ein Event ist eine Aktion oder ein Vorkommnis:

  • Benutzer klickt einen Button
  • Benutzer tippt in ein Eingabefeld
  • Seite ist fertig geladen
  • Maus bewegt sich über ein Element
  • Formular wird abgeschickt

Event Listener hinzufügen

addEventListener - Der Standard

const button = document.querySelector("#my-button");

// Event Listener hinzufügen
button.addEventListener("click", function(event) {
    console.log("Button wurde geklickt!");
    console.log("Event-Objekt:", event);
});

// Mit Arrow Function
button.addEventListener("click", (e) => {
    console.log("Geklickt!");
});

// Benannte Funktion (für removeEventListener)
function handleClick(e) {
    console.log("Geklickt!");
}
button.addEventListener("click", handleClick);

Event Listener entfernen

function handleClick(e) {
    console.log("Geklickt!");
}

// Hinzufügen
button.addEventListener("click", handleClick);

// Entfernen - MUSS dieselbe Funktion sein!
button.removeEventListener("click", handleClick);

// Das funktioniert NICHT:
button.addEventListener("click", () => console.log("Hi"));
button.removeEventListener("click", () => console.log("Hi"));  // Andere Funktion!

Optionen

// Einmal ausführen
button.addEventListener("click", () => {
    console.log("Nur einmal!");
}, { once: true });

// Capture Phase (statt Bubble)
element.addEventListener("click", handler, { capture: true });
// oder kurz:
element.addEventListener("click", handler, true);

// Passive (Performance für scroll/touch)
window.addEventListener("scroll", handler, { passive: true });

Das Event-Objekt

Jeder Event Handler bekommt ein Event-Objekt:

button.addEventListener("click", (event) => {
    // Allgemeine Eigenschaften
    console.log(event.type);        // "click"
    console.log(event.target);      // Das geklickte Element
    console.log(event.currentTarget); // Das Element mit dem Listener
    console.log(event.timeStamp);   // Zeitpunkt

    // Maus-Position
    console.log(event.clientX, event.clientY);  // Im Viewport
    console.log(event.pageX, event.pageY);      // Im Dokument

    // Modifier-Tasten
    console.log(event.ctrlKey);     // Ctrl gedrückt?
    console.log(event.shiftKey);    // Shift gedrückt?
    console.log(event.altKey);      // Alt gedrückt?
    console.log(event.metaKey);     // Cmd/Win gedrückt?
});

Häufige Event-Typen

Maus-Events

const element = document.querySelector(".box");

// Klick
element.addEventListener("click", (e) => {
    console.log("Klick!");
});

// Doppelklick
element.addEventListener("dblclick", (e) => {
    console.log("Doppelklick!");
});

// Maus rein/raus
element.addEventListener("mouseenter", (e) => {
    console.log("Maus rein");
});

element.addEventListener("mouseleave", (e) => {
    console.log("Maus raus");
});

// Maus bewegt sich
element.addEventListener("mousemove", (e) => {
    console.log(`Position: ${e.clientX}, ${e.clientY}`);
});

// Maus-Tasten
element.addEventListener("mousedown", (e) => {
    console.log("Taste gedrückt");
});

element.addEventListener("mouseup", (e) => {
    console.log("Taste losgelassen");
});

// Rechtsklick
element.addEventListener("contextmenu", (e) => {
    e.preventDefault();  // Standard-Menü verhindern
    console.log("Rechtsklick!");
});

Tastatur-Events

const input = document.querySelector("input");

// Taste gedrückt (wiederholt bei gedrückt halten)
input.addEventListener("keydown", (e) => {
    console.log("Taste:", e.key);       // "a", "Enter", "Escape"
    console.log("Code:", e.code);       // "KeyA", "Enter", "Escape"

    // Bestimmte Taste prüfen
    if (e.key === "Enter") {
        console.log("Enter gedrückt!");
    }

    if (e.key === "Escape") {
        console.log("Escape gedrückt!");
    }

    // Kombination prüfen
    if (e.ctrlKey && e.key === "s") {
        e.preventDefault();  // Browser-Speichern verhindern
        console.log("Strg+S gedrückt!");
    }
});

// Taste losgelassen
input.addEventListener("keyup", (e) => {
    console.log("Losgelassen:", e.key);
});

Formular-Events

const form = document.querySelector("form");
const input = document.querySelector("input");
const select = document.querySelector("select");

// Formular abschicken
form.addEventListener("submit", (e) => {
    e.preventDefault();  // Seiten-Reload verhindern!
    console.log("Formular abgeschickt");

    // Formular-Daten holen
    const formData = new FormData(form);
    console.log(formData.get("email"));
});

// Input-Wert ändert sich (bei jedem Zeichen)
input.addEventListener("input", (e) => {
    console.log("Aktueller Wert:", e.target.value);
});

// Wert geändert (bei Verlassen des Feldes)
input.addEventListener("change", (e) => {
    console.log("Neuer Wert:", e.target.value);
});

// Fokus
input.addEventListener("focus", () => {
    console.log("Input hat Fokus");
});

input.addEventListener("blur", () => {
    console.log("Input hat Fokus verloren");
});

// Select-Änderung
select.addEventListener("change", (e) => {
    console.log("Ausgewählt:", e.target.value);
});

Fenster-Events

// Seite geladen
window.addEventListener("load", () => {
    console.log("Alles geladen (inkl. Bilder)");
});

// DOM bereit (schneller als load)
document.addEventListener("DOMContentLoaded", () => {
    console.log("DOM bereit");
});

// Fenstergröße geändert
window.addEventListener("resize", () => {
    console.log(`Neue Größe: ${window.innerWidth}x${window.innerHeight}`);
});

// Gescrollt
window.addEventListener("scroll", () => {
    console.log("Scroll-Position:", window.scrollY);
});

// Seite verlassen (Warnung zeigen)
window.addEventListener("beforeunload", (e) => {
    e.preventDefault();
    e.returnValue = "";  // Browser-Dialog
});

Event Propagation (Bubbling & Capturing)

Events “wandern” durch den DOM:

<div id="outer">
    <div id="inner">
        <button id="button">Klick</button>
    </div>
</div>
// Alle drei reagieren auf den Button-Klick!
document.querySelector("#outer").addEventListener("click", () => {
    console.log("Outer");
});

document.querySelector("#inner").addEventListener("click", () => {
    console.log("Inner");
});

document.querySelector("#button").addEventListener("click", () => {
    console.log("Button");
});

// Ausgabe bei Klick auf Button:
// "Button"
// "Inner"
// "Outer"
// (Event "bubbelt" nach oben)

stopPropagation - Bubbling stoppen

document.querySelector("#button").addEventListener("click", (e) => {
    e.stopPropagation();  // Nicht weitergeben!
    console.log("Button");
});

// Jetzt nur:
// "Button"
// (Outer und Inner werden nicht ausgeführt)

Capturing Phase

// Mit { capture: true } oder true als 3. Parameter
document.querySelector("#outer").addEventListener("click", () => {
    console.log("Outer Capture");
}, true);

// Reihenfolge jetzt:
// 1. Capturing (von oben nach unten)
// 2. Target
// 3. Bubbling (von unten nach oben)

Event Delegation

Statt an jedes Element einzeln, an den Container hängen:

// Schlecht: Listener an jedes Item
document.querySelectorAll(".item").forEach(item => {
    item.addEventListener("click", () => {
        console.log("Item geklickt");
    });
});
// Problem: Neue Items haben keinen Listener!

// Gut: Event Delegation
document.querySelector(".item-list").addEventListener("click", (e) => {
    // Prüfen ob ein Item geklickt wurde
    const item = e.target.closest(".item");
    if (item) {
        console.log("Item geklickt:", item);
    }
});
// Funktioniert auch für neue Items!

Praktisches Beispiel: Todo-Liste mit Delegation

const todoList = document.querySelector("#todo-list");

// Ein Listener für alles!
todoList.addEventListener("click", (e) => {
    const target = e.target;

    // Delete-Button geklickt?
    if (target.classList.contains("delete-btn")) {
        const todoItem = target.closest(".todo-item");
        todoItem.remove();
        return;
    }

    // Checkbox geklickt?
    if (target.classList.contains("todo-checkbox")) {
        const todoItem = target.closest(".todo-item");
        todoItem.classList.toggle("completed");
        return;
    }

    // Todo-Text geklickt?
    if (target.classList.contains("todo-text")) {
        // Bearbeiten starten
        startEditing(target);
    }
});

preventDefault - Standard verhindern

// Link-Navigation verhindern
document.querySelector("a").addEventListener("click", (e) => {
    e.preventDefault();
    console.log("Link geklickt, aber nicht navigiert");
});

// Formular-Submit verhindern
document.querySelector("form").addEventListener("submit", (e) => {
    e.preventDefault();
    console.log("Formular nicht abgeschickt");
});

// Rechtsklick-Menü verhindern
document.addEventListener("contextmenu", (e) => {
    e.preventDefault();
});

// Bestimmte Tastenkombinationen verhindern
document.addEventListener("keydown", (e) => {
    if (e.ctrlKey && e.key === "s") {
        e.preventDefault();
        saveDocument();  // Eigene Speicher-Funktion
    }
});

Praktische Beispiele

Drag & Drop (vereinfacht)

const draggable = document.querySelector(".draggable");
let isDragging = false;
let offsetX, offsetY;

draggable.addEventListener("mousedown", (e) => {
    isDragging = true;
    offsetX = e.clientX - draggable.offsetLeft;
    offsetY = e.clientY - draggable.offsetTop;
    draggable.style.cursor = "grabbing";
});

document.addEventListener("mousemove", (e) => {
    if (!isDragging) return;

    draggable.style.left = (e.clientX - offsetX) + "px";
    draggable.style.top = (e.clientY - offsetY) + "px";
});

document.addEventListener("mouseup", () => {
    isDragging = false;
    draggable.style.cursor = "grab";
});

Infinite Scroll

window.addEventListener("scroll", () => {
    // Prüfen ob am Ende der Seite
    const scrollTop = window.scrollY;
    const windowHeight = window.innerHeight;
    const documentHeight = document.documentElement.scrollHeight;

    if (scrollTop + windowHeight >= documentHeight - 100) {
        loadMoreContent();
    }
});

function loadMoreContent() {
    // Neue Inhalte laden...
    console.log("Lade mehr...");
}

Keyboard Navigation

const items = document.querySelectorAll(".menu-item");
let currentIndex = 0;

document.addEventListener("keydown", (e) => {
    if (e.key === "ArrowDown") {
        e.preventDefault();
        currentIndex = Math.min(currentIndex + 1, items.length - 1);
        items[currentIndex].focus();
    }

    if (e.key === "ArrowUp") {
        e.preventDefault();
        currentIndex = Math.max(currentIndex - 1, 0);
        items[currentIndex].focus();
    }

    if (e.key === "Enter") {
        items[currentIndex].click();
    }
});

Debounce für Performance

function debounce(fn, delay) {
    let timeoutId;
    return function(...args) {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => fn.apply(this, args), delay);
    };
}

// Ohne Debounce - bei jedem Zeichen
input.addEventListener("input", (e) => {
    search(e.target.value);  // Zu viele Aufrufe!
});

// Mit Debounce - 300ms nach letzter Eingabe
input.addEventListener("input", debounce((e) => {
    search(e.target.value);
}, 300));

Throttle für Scroll

function throttle(fn, limit) {
    let inThrottle;
    return function(...args) {
        if (!inThrottle) {
            fn.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

// Max alle 100ms ausführen
window.addEventListener("scroll", throttle(() => {
    console.log("Scroll:", window.scrollY);
}, 100));

Custom Events

// Custom Event erstellen
const myEvent = new CustomEvent("userLoggedIn", {
    detail: {
        userId: 123,
        username: "max"
    },
    bubbles: true
});

// Event dispatchen
document.dispatchEvent(myEvent);

// Event hören
document.addEventListener("userLoggedIn", (e) => {
    console.log("User eingeloggt:", e.detail.username);
});

Zusammenfassung

EventBeschreibung
clickMausklick
dblclickDoppelklick
mouseenter/leaveMaus rein/raus
keydown/keyupTaste drücken/loslassen
inputWert ändert sich
changeWert geändert (bei Blur)
submitFormular abschicken
focus/blurFokus erhalten/verlieren
scrollScrollen
resizeFenstergröße
// Merksatz:
element.addEventListener("eventType", (event) => {
    event.preventDefault();     // Standard verhindern
    event.stopPropagation();    // Bubbling stoppen
    event.target;               // Geklicktes Element
    event.currentTarget;        // Element mit Listener
});

Übung: Baue eine Bildergalerie mit Keyboard-Navigation: Pfeiltasten zum Navigieren, Escape zum Schließen, und Klick auf Thumbnail zum Öffnen.