Die meisten Tutorials erklären „onclick” — und hören da auf. Reale Anwendungen brauchen aber Tastatur-Shortcuts, Drag-Verhalten, Touch-Gesten und kontrolliertes Focus-Management. Dieser Artikel geht durch alle wichtigen DOM-Event-Familien, immer mit einem Mini-Beispiel, das man auch mit wenig DOM-Erfahrung nachvollziehen kann.
Tastatur-Events – keydown, keyup, keypress
Wenn der Nutzer eine Taste drückt, gibt es drei Events in dieser Reihenfolge: keydown (Taste runter), keypress (eingegebenes Zeichen — gilt als veraltet) und keyup (Taste wieder hoch). In der Praxis nutzt man fast immer keydown.
<script>
let lastKey = $state('—');
function handleKey(event) {
lastKey = event.key;
}
</script>
<input onkeydown={handleKey} placeholder="Tippe etwas …" />
<p>Zuletzt gedrückt: <strong>{lastKey}</strong></p>Der Event hat zwei wichtige Eigenschaften:
event.key— was du als Mensch siehst:'a','A','Enter','ArrowLeft','Escape'.event.code— die physische Taste auf der Tastatur, unabhängig vom Layout:'KeyA','Enter','ArrowLeft'. Nützlich für Spiele oder Shortcuts, die immer auf derselben Taste liegen sollen, egal ob US- oder DE-Layout.
Im Alltag reicht event.key.
Tastatur-Shortcuts (z. B. Cmd/Ctrl + S)
Praktisches Beispiel: „Speichern” mit Cmd+S oder Strg+S abfangen.
<script>
function handleSaveShortcut(event) {
const isSaveCombo = event.key === 's' && (event.metaKey || event.ctrlKey);
if (!isSaveCombo) return;
event.preventDefault(); // Browser-„Seite speichern"-Dialog verhindern
console.log('App-eigenes Speichern auslösen');
}
</script>
<svelte:window onkeydown={handleSaveShortcut} />Drei Punkte zum Verstehen:
metaKeyist ⌘ auf macOS,ctrlKeyist Strg auf Windows/Linux. Beides oder-verknüpft funktioniert plattformübergreifend.event.preventDefault()verhindert den Browser-Default — sonst würde der „Seite speichern”-Dialog aufgehen.<svelte:window>lauscht auf Window-Ebene — der Listener funktioniert, egal welches Element gerade Fokus hat.
Weitere Modifier-Tasten
| Eigenschaft | Bedeutung |
|---|---|
event.shiftKey | Umschalt |
event.altKey | Alt / Option |
event.ctrlKey | Strg |
event.metaKey | ⌘ (macOS) / Win-Taste (Windows) |
Häufige Tasten-Werte
event.key als Vergleichswert ist string-basiert. Die wichtigsten Schlüsselwerte:
event.key | Bedeutung |
|---|---|
'Enter' | Eingabetaste |
'Escape' | Esc |
'Tab' | Tab |
'Backspace' | Rückschritt |
'Delete' | Entf |
'ArrowUp'/'ArrowDown'/'ArrowLeft'/'ArrowRight' | Pfeiltasten |
' ' (Leerzeichen) | Space |
'a'-'z', '0'-'9' | Buchstaben/Zahlen |
<script>
let { open = $bindable(false) } = $props();
function handleKey(event) {
if (event.key === 'Escape') open = false;
}
</script>
{#if open}
<svelte:window onkeydown={handleKey} />
<div class="modal">…</div>
{/if}Maus-Events – click, mousedown, mouseup, mousemove
click deckt 90 % aller Klicks ab. Wenn du vor dem Loslassen schon etwas tun willst (typisch bei Drag-Operationen), gibt es das feinere Geschwister-Trio:
mousedown— Maustaste gedrückt.mousemove— Maus bewegt sich.mouseup— Maustaste losgelassen.
<script>
let dragging = $state(false);
let x = $state(0);
let y = $state(0);
function start() {
dragging = true;
}
function move(event) {
if (!dragging) return;
x = event.clientX;
y = event.clientY;
}
function stop() {
dragging = false;
}
</script>
<svelte:window onmousemove={move} onmouseup={stop} />
<div
class="box"
style="transform: translate({x}px, {y}px)"
onmousedown={start}
>
ziehe mich
</div>Wichtig: mousemove und mouseup werden auf Window gehört, nicht auf der Box. Sonst verlierst du das Tracking, sobald die Maus die Box verlässt.
Position im Event
Das Event-Objekt hat mehrere Koordinaten-Paare:
clientX/clientY— relativ zum sichtbaren Browser-Fenster.pageX/pageY— relativ zur gesamten Seite (mit Scroll).offsetX/offsetY— relativ zum Element, auf dem das Event ausgelöst wurde.
In den meisten Fällen reicht clientX/clientY.
Pointer-Events – die moderne Vereinheitlichung
Smartphones und Tablets haben weder Maus noch Tastatur — sondern Touch und Stylus. Damit du nicht für jede Eingabeart eigene Handler schreiben musst, gibt es seit ein paar Jahren Pointer-Events: ein vereinheitlichter Mechanismus für Maus, Touch und Stift.
| Maus-Event | Pointer-Äquivalent |
|---|---|
mousedown | pointerdown |
mousemove | pointermove |
mouseup | pointerup |
mouseenter | pointerenter |
mouseleave | pointerleave |
Empfehlung: Bei neuem Code Pointer-Events nutzen. Der gleiche Code funktioniert dann automatisch mit Touch-Geräten.
<script>
let x = $state(0);
let y = $state(0);
function startDrag(event) {
event.target.setPointerCapture(event.pointerId);
}
function drag(event) {
if (!event.buttons) return; // nur wenn gedrückt
x = event.clientX;
y = event.clientY;
}
</script>
<div
class="handle"
style="transform: translate({x}px, {y}px)"
onpointerdown={startDrag}
onpointermove={drag}
>
zieh mich
</div>setPointerCapture(event.pointerId) ist der Trick, der das Drag „klebrig” macht: Auch wenn der Pointer das Element verlässt, kommen weiterhin pointermove-Events bei diesem Element an. Kein Window-Listener mehr nötig.
Welche Eingabeart war es?
Pointer-Events tragen die Information mit:
<div onpointerdown={(e) => console.log(e.pointerType)}>
…
</div>event.pointerType ist 'mouse', 'touch' oder 'pen'.
Touch-Events (selten, aber manchmal nötig)
Wenn dein Code wirklich nur auf Touch-Geräten laufen muss oder du Multi-Touch brauchst (zwei Finger gleichzeitig), gibt es noch die klassischen Touch-Events: touchstart, touchmove, touchend. Sie liefern eine touches-Liste mit allen aktuell aktiven Finger-Positionen.
<script>
function handleTouch(event) {
if (event.touches.length === 2) {
const [a, b] = event.touches;
const distance = Math.hypot(b.clientX - a.clientX, b.clientY - a.clientY);
console.log('Zwei Finger, Abstand:', distance);
}
}
</script>
<div ontouchmove={handleTouch}>
…
</div>Für die meisten Anwendungen reichen Pointer-Events vollkommen aus.
Focus-Events – focus, blur, focusin, focusout
Wenn ein Eingabefeld den Fokus bekommt oder verliert, kannst du mit focus/blur reagieren:
<script>
function selectAll(event) {
event.target.select();
}
</script>
<input value="Doppelklick auf mich" onfocus={selectAll} />Der wichtige Unterschied: focus vs. focusin
focus und blur bubblen nicht — sie feuern nur am direkt fokussierten Element. Wenn du auf einem Eltern-Element wissen willst, ob irgendein Kind den Fokus hat, brauchst du focusin und focusout (die bubblen).
<script>
let hasFocus = $state(false);
</script>
<div
class="form-group"
class:active={hasFocus}
onfocusin={() => hasFocus = true}
onfocusout={() => hasFocus = false}
>
<input />
<button>OK</button>
</div>
<style>
.form-group.active { border-color: teal; }
</style>Egal ob <input> oder <button> Fokus hat — die <div> weiß es.
Click-Outside-Pattern (häufig nachgefragt)
Eines der meist gefragten Patterns: „Ein Dropdown soll sich schließen, wenn der Nutzer außerhalb klickt.” Das geht so:
<script>
let open = $state(false);
let container;
function handleWindowClick(event) {
if (open && !container.contains(event.target)) {
open = false;
}
}
</script>
<svelte:window onclick={handleWindowClick} />
<div bind:this={container}>
<button onclick={() => open = !open}>Menü</button>
{#if open}
<ul class="menu">
<li>Eintrag 1</li>
<li>Eintrag 2</li>
</ul>
{/if}
</div>Schritt für Schritt:
- Der Window-Listener fängt jeden Klick auf der Seite ab.
container.contains(event.target)prüft, ob der Klick innerhalb des Dropdown-Containers war.- Wenn nicht (außerhalb), wird das Dropdown geschlossen.
Eleganter als manuelles Listener-Verwalten — und voll reaktiv.
Häufige Stolperfallen
event.preventDefault() zu spät aufgerufen.
Bei onsubmit und onkeydown muss preventDefault() synchron gerufen werden — nicht erst in einem await-Callback. Sonst ist es zu spät.
focus und blur mit Bubbling erwartet.
Sie bubblen nicht. Für Container-übergreifendes Tracking brauchst du focusin/focusout.
mouseup außerhalb des Elements verloren.
Wenn die Maus das Element verlässt, bevor losgelassen wird, kommt das mouseup nicht mehr an. Lösung: Listener auf <svelte:window> legen oder Pointer-Events mit setPointerCapture.
Tastatur-Shortcuts in Inputs.
Wenn der Nutzer im Input tippt, willst du Shortcuts oft nicht abfangen. Prüfen mit event.target.tagName === 'INPUT' oder document.activeElement.
Modifier-Keys vergessen.
Wer einen Save-Shortcut nur auf event.key === 's' prüft, fängt jedes „s” beim Tippen. Immer zusätzlich auf metaKey/ctrlKey prüfen.