Den Weg in die Zukunft weisen

Sérgio Gomes

Früher war es ganz einfach, im Web auf Dinge zu zeigen. Man hat eine Maus bewegt, sie bewegt, manchmal Tasten gedrückt – und das war es. Alles, was keine Maus war, wurde emuliert und die Entwickler wussten genau, worauf sie zählen mussten.

Einfach bedeutet aber nicht unbedingt gut. Im Laufe der Zeit wurde es immer wichtiger, dass nicht alles eine Maus war (oder vorgeben würde, eine Maus zu sein): Sie konnten druckempfindliche und neigende Stifte verwenden, um erstaunliche kreative Freiheit zu haben. Sie konnten nur mit den Fingern das Gerät und Ihre Hand verwenden. Warum nicht auch mit mehr als einem Finger, wenn Sie dabei sind?

Wir nutzen schon seit einiger Zeit Touch-Ereignisse, um uns dabei zu unterstützen. Dabei handelt es sich jedoch um eine völlig separate API speziell für Touch-Ereignisse. Daher müssen Sie zwei separate Ereignismodelle codieren, wenn Sie sowohl Maus als auch Touch unterstützen möchten. Chrome 55 wird mit einem neueren Standard ausgeliefert, der beide Modelle vereinheitlicht: Zeigerereignisse.

Ein einzelnes Ereignismodell

Zeiger-Ereignisse vereinheitlichen das Zeigereingabemodell für den Browser. Berührungen, Stifte und Mäuse werden zu einem einzigen Ereignissatz zusammengeführt. 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 sollte, wenn Sie mit Mausereignissen vertraut sind:

pointerover Der Zeiger hat den Begrenzungsrahmen des Elements erreicht. Dies geschieht sofort bei Geräten, die Hover-Funktionen unterstützen, bzw. vor einem pointerdown-Ereignis bei Geräten, bei denen das nicht der Fall ist.
pointerenter Ähnlich wie pointerover, aber es werden keine Bubbles angezeigt und Nachfolgerelemente werden unterschiedlich behandelt. Details zur Spezifikation
pointerdown Der Zeiger hat den aktiven Schaltflächenstatus erreicht und je nach Semantik des Eingabegeräts entweder eine Schaltfläche gedrückt oder ein Kontakt hergestellt.
pointermove Die Position des Mauszeigers hat sich geändert.
pointerup Der Zeiger hat den aktiven Schaltflächenstatus verlassen.
pointercancel Ein Fehler ist aufgetreten, was bedeutet, dass der Zeiger wahrscheinlich keine weiteren Ereignisse ausgeben wird. Dies bedeutet, dass Sie alle laufenden Aktionen abbrechen und zu einem neutralen Eingabestatus zurückkehren sollten.
pointerout Der Zeiger hat den Begrenzungsrahmen des Elements oder Bildschirms verlassen. Auch nach einem pointerup, wenn das Gerät den Mauszeiger nicht unterstützt.
pointerleave Ähnlich wie pointerout, aber es werden keine Bubbles angezeigt und Nachfolgerelemente werden unterschiedlich behandelt. Details zur Spezifikation
gotpointercapture Element hat Pointer-Capture erhalten.
lostpointercapture Der Zeiger, der erfasst wurde, wurde freigegeben.

Verschiedene Eingabetypen

Im Allgemeinen können Sie mit Zeigerereignissen Code eingabeunabhängig schreiben, ohne separate Event-Handler für verschiedene Eingabegeräte registrieren zu müssen. Natürlich müssen Sie trotzdem die Unterschiede zwischen den Eingabetypen beachten, z. B. ob das Konzept des Hover-Modus angewendet werden kann. Wenn Sie verschiedene Eingabegerätetypen voneinander unterscheiden möchten, beispielsweise um separaten Code bzw. separaten Funktionen für unterschiedliche Eingaben bereitzustellen, können Sie dies innerhalb desselben Event-Handlers tun. Verwenden Sie dazu das Attribut pointerType der PointerEvent-Schnittstelle. Wenn Sie beispielsweise eine seitliche Navigationsleiste programmieren, können Sie für Ihr pointermove-Ereignis die folgende Logik verwenden:

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 Browsern mit Touchscreen werden bestimmte Touch-Gesten verwendet, um Seiten zu scrollen, zu zoomen oder zu aktualisieren. Bei Touch-Ereignissen erhältst du weiterhin Ereignisse, während diese Standardaktionen ausgeführt werden. So wird beispielsweise touchmove weiterhin ausgelöst, während der Nutzer scrollt.

Wenn Zeigerereignisse eine Standardaktion wie Scrollen oder Zoomen ausgelöst werden, erhalten Sie ein pointercancel-Ereignis, das Sie darüber informiert, 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.'));

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

Wenn Sie verhindern möchten, dass der Browser die Kontrolle übernimmt, verwenden Sie die CSS-Eigenschaft touch-action. Wenn Sie es für ein Element auf none festlegen, werden alle browserdefinierten Aktionen deaktiviert, die für dieses Element gestartet werden. Es gibt jedoch eine Reihe anderer Werte für eine feinere Steuerung, z. B. pan-x, mit denen der Browser auf Bewegungen auf der x-Achse, aber nicht auf der y-Achse reagieren kann. Chrome 55 unterstützt die folgenden Werte:

auto Standardeinstellung: Der Browser kann jede Standardaktion ausführen.
none Der Browser ist nicht berechtigt, Standardaktionen auszuführen.
pan-x Der Browser darf nur die Standardaktion horizontales Scrollen ausführen.
pan-y Der Browser darf nur die Standardaktion für vertikales Scrollen ausführen.
pan-left Der Browser darf nur die Standardaktion horizontales Scrollen ausführen und die Seite nur nach links schwenken.
pan-right Der Browser darf nur die Standardaktion horizontales Scrollen ausführen und die Seite nur nach rechts schwenken.
pan-up Der Browser darf nur die Standardaktion vertikales Scrollen ausführen und die Seite nur nach oben schwenken.
pan-down Der Browser darf nur die Standardaktion vertikales Scrollen ausführen und die Seite nur nach unten schwenken.
manipulation Der Browser darf nur Scroll- und Zoomaktionen ausführen.

Zeigererfassung

Haben Sie schon einmal frustrierte Stunden mit der Fehlerbehebung eines fehlerhaften mouseup-Ereignisses verbracht, bis Sie feststellen, dass dies daran liegt, dass der Nutzer die Schaltfläche außerhalb Ihres Klickziels loslässt? Nein? Okay, dann sind es vielleicht nur ich.

Bisher gab es jedoch keine wirklich gute Lösung, um 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 versuchen, alles sauber und isoliert zu halten.

Zeigerereignisse ist eine viel bessere Lösung: Sie können den Zeiger erfassen, sodass Sie auf jeden Fall das pointerup-Ereignis (oder alle anderen Freunde, die es nicht kennen) 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

Derzeit werden Zeigerereignisse in Internet Explorer 11, Microsoft Edge, Chrome und Opera sowie teilweise in Firefox unterstützt. Eine aktuelle Liste finden Sie unter caniuse.com.

Mit dem Polyfill für Zeigerereignisse können Sie die Lücken schließen. Alternativ lässt sich die Browserunterstützung während der Laufzeit einfach prüfen:

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

Zeigerereignisse eignen sich hervorragend für Progressive Enhancement: Ändern Sie einfach Ihre Initialisierungsmethoden so, dass die obige Prüfung durchgeführt wird, fügen Sie dem if-Block Zeiger-Event-Handler hinzu und verschieben Sie die Maus-/Touch-Event-Handler in den else-Block.

Probieren Sie sie einfach aus und teilen Sie uns Ihre Meinung mit.