Den Weg in die Zukunft weisen

Sérgio Gomes

Es war einmal ganz einfach, im Web auf Dinge zu zeigen. Sie hatten eine Maus, haben sie bewegt, manchmal Tasten gedrückt und das war es. Alles, was keine Maus war, wurde als Maus emuliert und die Entwickler wussten genau, worauf sie sich verlassen konnten.

Einfach bedeutet aber nicht unbedingt gut. Im Laufe der Zeit wurde es immer wichtiger, dass nicht alles eine Maus war (oder vorgab, eine zu sein): Sie konnten druckempfindliche und Neigungssensoren verwenden, um eine enorme kreative Freiheit zu erhalten. Sie konnten Ihre Finger verwenden, sodass Sie nur das Gerät und Ihre Hand benötigten. Und warum nicht gleich mehrere Finger verwenden?

Wir haben schon seit einiger Zeit Touch-Ereignisse, die uns dabei helfen. Es handelt sich jedoch um eine völlig separate API speziell für Touchbedienung. Wenn Sie sowohl Maus- als auch Touchbedienung unterstützen möchten, müssen Sie also zwei separate Ereignismodelle programmieren. Chrome 55 unterstützt einen neueren Standard, der beide Modelle vereint: Zeigerereignisse.

Ein Modell für ein einzelnes Ereignis

Maus-Ereignisse vereinheitlichen das Eingabemodell für den Mauszeiger im Browser und fassen Touchbedienung, Eingabestifte und Mäuse in einer einzigen Gruppe von Ereignissen zusammen. Beispiel:

document.addEventListener('pointermove',
    ev => console.log('The pointer moved.'));
foo.addEventListener('pointerover',
    ev => console.log('The pointer is now over foo.'));

Hier ist eine Liste aller verfügbaren Ereignisse, die Ihnen bekannt vorkommen sollten, wenn Sie mit Mausereignissen vertraut sind:

pointerover Der Cursor befindet sich im Begrenzungsrahmen des Elements. Das geschieht sofort auf Geräten, die das Hovering unterstützen, oder vor einem pointerdown-Ereignis auf Geräten, die das Hovering nicht unterstützen.
pointerenter Ähnlich wie pointerover, aber ohne Aufsteigen und mit anderer Behandlung von Nachkommen. Details zur Spezifikation
pointerdown Der Cursor befindet sich im Status „Aktive Schaltfläche“. Je nach Semantik des Eingabegeräts wird entweder eine Schaltfläche gedrückt oder eine Berührung erkannt.
pointermove Der Cursor hat seine Position geändert.
pointerup Der Cursor hat den Status der aktiven Schaltfläche verlassen.
pointercancel Es ist etwas passiert, was bedeutet, dass der Zeiger wahrscheinlich keine weiteren Ereignisse mehr ausgibt. Sie sollten also alle laufenden Aktionen abbrechen und zu einem neutralen Eingabestatus zurückkehren.
pointerout Der Cursor hat den Begrenzungsrahmen des Elements oder des Bildschirms verlassen. Auch nach einem pointerup, wenn das Gerät das Hovering nicht unterstützt.
pointerleave Ähnlich wie pointerout, aber ohne Aufsteigen und mit anderer Behandlung von Nachkommen. Details zur Spezifikation
gotpointercapture Das Element hat eine Maus- oder Touch-Eingabe erhalten.
lostpointercapture Der aufgenommene Zeiger wurde freigegeben.

Verschiedene Eingabetypen

Im Allgemeinen ermöglichen Pointer-Ereignisse, Code unabhängig von der Eingabe zu schreiben, ohne dass separate Ereignishandler für verschiedene Eingabegeräte registriert werden müssen. Natürlich müssen Sie die Unterschiede zwischen den Eingabetypen berücksichtigen, z. B. ob das Konzept „Hover“ gilt. Wenn Sie verschiedene Eingabegerätetypen unterscheiden möchten, z. B. um für verschiedene Eingaben separaten Code/Funktionen bereitzustellen, können Sie dies jedoch innerhalb derselben Ereignishandler mithilfe der Eigenschaft pointerType der Schnittstelle PointerEvent tun. Wenn Sie beispielsweise eine seitliche Navigationsleiste codieren, könnte die folgende Logik für das Ereignis pointermove gelten:

switch(ev.pointerType) {
    case 'mouse':
    // Do nothing.
    break;
    case 'touch':
    // Allow drag gesture.
    break;
    case 'pen':
    // Also allow drag gesture.
    break;
    default:
    // Getting an empty string means the browser doesn't know
    // what device type it is. Let's assume mouse and do nothing.
    break;
}

Standardaktionen

In Touch-fähigen Browsern werden bestimmte Touch-Gesten verwendet, um die Seite zu scrollen, zu zoomen oder zu aktualisieren. Bei Touch-Ereignissen erhalten Sie weiterhin Ereignisse, während diese Standardaktionen ausgeführt werden. Beispielsweise wird touchmove weiterhin ausgelöst, während der Nutzer scrollt.

Bei Zeigerereignissen erhalten Sie jedes Mal ein pointercancel-Ereignis, wenn eine Standardaktion wie Scrollen oder Zoomen ausgelöst wird. So erkennen Sie, dass der Browser die Kontrolle über den Zeiger übernommen hat. Beispiel:

document.addEventListener('pointercancel',
    ev => console.log('Go home, the browser is in charge now.'));

Eingebaute Geschwindigkeit: Dieses Modell bietet standardmäßig eine bessere Leistung als Touch-Ereignisse, bei denen Sie passive Ereignis-Listener verwenden müssten, um dieselbe Reaktionsfähigkeit zu erreichen.

Mit der CSS-Eigenschaft touch-action können Sie verhindern, dass der Browser die Kontrolle übernimmt. Wenn Sie für ein Element den Wert none festlegen, werden alle browserdefinierten Aktionen deaktiviert, die über dieses Element gestartet werden. Es gibt jedoch eine Reihe anderer Werte für eine detailliertere Steuerung, z. B. pan-x, mit dem der Browser auf Bewegungen entlang der X‑Achse, aber nicht der Y‑Achse reagieren kann. Chrome 55 unterstützt die folgenden Werte:

auto Standard; der Browser kann eine beliebige Standardaktion ausführen.
none Der Browser darf keine Standardaktionen ausführen.
pan-x Der Browser darf nur die Standardaktion „Horizontal scrollen“ ausführen.
pan-y Der Browser darf nur die Standardaktion für das vertikale Scrollen ausführen.
pan-left Der Browser darf nur die Standardaktion für das horizontale Scrollen ausführen und die Seite nur nach links schwenken.
pan-right Der Browser darf nur die Standardaktion „Horizontal scrollen“ ausführen und die Seite nur nach rechts schwenken.
pan-up Der Browser darf nur die Standardaktion für das vertikale Scrollen ausführen und nur die Seite nach oben schwenken.
pan-down Der Browser darf nur die Standardaktion für das vertikale Scrollen ausführen und nur die Seite nach unten schwenken.
manipulation Der Browser darf nur Scroll- und Zoomaktionen ausführen.

Zeigererfassung

Haben Sie schon einmal eine frustrierende Stunde damit verbracht, ein fehlerhaftes mouseup-Ereignis zu beheben, bis Sie festgestellt haben, dass der Nutzer die Schaltfläche außerhalb Ihres Klickziels loslässt? Nein? Okay, vielleicht liegt es nur an mir.

Bislang gab es jedoch keine wirklich gute Möglichkeit, dieses Problem anzugehen. Natürlich können Sie den mouseup-Handler für das Dokument einrichten und einen Status in Ihrer Anwendung speichern, um den Überblick zu behalten. Das ist jedoch nicht die sauberste Lösung, insbesondere wenn Sie eine Webkomponente erstellen und alles schön getrennt halten möchten.

Mit Zeigerereignissen gibt es eine viel bessere Lösung: Sie können den Zeiger erfassen, damit Sie das pointerup-Ereignis (oder einen anderen seiner schwer fassbaren Freunde) auf jeden Fall erhalten.

const foo = document.querySelector('#foo');
foo.addEventListener('pointerdown', ev => {
    console.log('Button down, capturing!');
    // Every pointer has an ID, which you can read from the event.
    foo.setPointerCapture(ev.pointerId);
});

foo.addEventListener('pointerup', 
    ev => console.log('Button up. Every time!'));

Unterstützte Browser

Zum Zeitpunkt der Erstellung dieses Artikels werden Pointer-Ereignisse in Internet Explorer 11, Microsoft Edge, Chrome und Opera unterstützt. In Firefox werden sie teilweise unterstützt. Eine aktuelle Liste finden Sie unter caniuse.com.

Sie können die Pointer Events-Polyfill verwenden, um die Lücken zu schließen. Alternativ können Sie die Browserunterstützung während der Laufzeit ganz einfach prüfen:

if (window.PointerEvent) {
    // Yay, we can use pointer events!
} else {
    // Back to mouse and touch events, I guess.
}

Zeiger-Ereignisse eignen sich hervorragend für die progressive Verbesserung: Ändern Sie einfach Ihre Initialisierungsmethoden, um die oben genannte Prüfung durchzuführen, fügen Sie Zeiger-Ereignis-Handler in den Block if ein und verschieben Sie die Maus-/Touch-Ereignis-Handler in den Block else.

Probieren Sie sie aus und lassen Sie uns wissen, was Sie davon halten.