Hinweis: Am 9. April beteiligen wir uns auf webkrauts.de am Naked CSS Day. Die Stylesheets sind an diesem Tag absichtlich abgeschaltet.

Webkrauts Logo

Webkrauts Webkrauts Schriftzug

- für mehr Qualität im Web

Webseiten zum Anfassen

Touch-Events

Webseiten zum Anfassen

Touchscreens auf Handys, Tablets und touch-fähigen Laptops eröffnen Entwicklern ganz eue Interaktions-Möglichkeiten. In dieser Einführung schauen wir uns die Grundlagen zur JavaScript-seitigen abhandlung von Touch-Events an.

Grundlagen zur Bearbeitung von Touch-Events

Mobile Browser sind von Haus aus darauf ausgerichtet, existierende Webseiten so gut wie möglich auf Handys und Tablets darzustellen und benutzbar zu machen. Da die meisten Seiten im Web Interaktion mittels Maus erwarten, simulieren Browser auch auf Touch-Geräten die meisten Maus-Events, die wir schon von jeher kennen.

Obwohl es kleinere Unterschiede zwischen den verschiedenen Browsern gibt, sieht das Ganze meistens so aus: wenn ein User auf den Touchscreen tapt, feuert der Browser eine Reihe an Events, um sicherzustellen, dass mausorientierte Scripts so wie auf traditionellen Desktops ausgeführt werden.

Screenshot Beispiel 1
Beispiel 1 – Simulierte Maus-Events
  1. mouseover > mousemove > mousedown > mouseup > click

Wie wir sehen, werden selbst auf Touchscreens die meisten Maus-Events abgefeuert: zuerst ein mouseover, als ob der User frisch mit der Maus auf das Element gefahren wäre; danach ein einziger mousemove, der wirklich nur aus Kompatibilität mit Scripts, die darauf reagieren, vorhanden ist; und zuletzt mousedown, mouseup und click, um das Drücken der Maustaste komplett zu simulieren. Man beachte, dass diese Events in dieser Reihenfolge praktisch ohne Zwischenpausen abgesandt werden.

Wenn wir also eine Webseite haben, die auf spezifische Events wie click oder mouseover reagiert, wird diese Seite in den meisten Fällen ohne jegliche Veränderung auch auf Touch-Geräten benutzbar sein. Aber wie so oft hat diese Event-Emulation im Browser – bedingt durch die besonderen Eigenschaften von Handys und Tablets – auch ihre Nachteile.

Aus diesem Grund bieten Browser auf touch-fähigen Geräten neben der Maus-Emulation eine Reihe an neuen, touch-spezifischen JavaScript Events. Diese Events wurden zuerst für iOS Safari eingeführt und im Nachhinein offiziell in der W3C Touch Events Spezifikation standardisiert.

Die neuen Events, die uns zur Verfügung stehen, sind: touchstart, touchmove, touchend, touchcancel. Die ersten drei Events sind die Touch-Alternativen zu den Maus-Events mousedown, mousemove und mouseup. touchcancel gibt hingegen an, dass eine Touch-Interaktion abgebrochen wurde – in den meisten Fällen, wenn der User den Finger außerhalb des Dokuments (zum Beispiel in die Browser-UI) bewegt.

Screenshot Beispiel 2
Beispiel 2 – Maus- und Touch-Events

Die Abfolge der Touch- und Maus-Events sieht in Browsern auf touch-fähigen Geräten nun so aus:

  1. touchstart > [touchmove]+ > touchend > mouseover > mousemove > mousedown > mouseup > click

Zuerst werden also die touch-spezifischen Events abgefeuert: touchstart, mindestens ein touchmove (bei leichter Fingerbewegung während des Taps können es aber auch mehr sein – man beachte aber, dass zuviel Bewegung den Browser dazu führt, die Berührung nicht mehr als Tap zu interpretieren, sondern als einen Swipe oder als Scroll-Geste, und somit wird die Event-Reihenfolge dann nach dem Hochheben des Fingers abgebrochen) und touchend. Danach werden, wie schon vorher gesehen, die simulierten Maus-Events abgesandt.

Obwohl diese Touch-Events in Firefox, Opera und Webkit-basierten Browsern unterstützt werden, gibt es eine Ausnahme: Microsoft hat in Internet Explorer 10 eine eigene Spezifikation für Pointer- und Gesture-Events eingeführt, und diese auch nachträglich zur Standardisierung beim W3C eingereicht. Nun gibt es seit dem 11. Dezember Microsofts Pointer-Events-Vorschlag auch schon als W3C Working Draft von der neuen W3C Pointer Events Working Group.

Microsofts Ansatz schlägt ein neues Event-Modell vor, das alle verschiedenen Input Methoden (Maus, Keyboard, Touch, usw.) zusammenfasst. Obwohl dieses Modell sehr vielversprechend ist, können diese neuen Events momentan nur im IE10 eingesetzt werden. Aus diesem Grund konzentrieren wir uns hier nur auf die spezifischen Touch-Events. In den weiterführenden Links am Ende des Artikels findet ihr aber Ressourcen, die es ermöglichen, beide Arten an Events zu unterstützen.

»Feature detection« für Touch-Events

Um programmatisch festzustellen, ob ein Browser touch-fähig ist, reicht ein einfacher JavaScript Test – es gibt hier mehrere Varianten, aber am häufigsten scheint diese Methode benutzt zu werden:

  1. if ('ontouchstart' in window) { /* Touch Unterstützung vorhanden */ }

Hierbei ist allerdings Vorsicht geboten: nur weil ein Browser angibt, dass Touch-Events vorhanden sind, heisst dies noch lange nicht, dass eine Webseite nur exklusiv auf Touch-Events reagieren sollte: spätestens seit der Einführung von Windows 8 gibt es jetzt hybride Laptops, die sowohl Trackpad und Keyboard als auch einen Touchscreen haben. Nur weil ein Browser auf einem touch-fähigen Gerät läuft, sollte man also trotzdem immer noch auf die traditionellen Interaktionsmethoden achten.

Verkürzte Reaktionszeit bei Events

Im Gegensatz zu Mausclicks, ist auf Touch-Geräten eine Berührung des Bildschirms nicht unbedingt gleich ein click. Um zu verhindern, dass Elemente unbeabsichtigt aktiviert werden, warten Browser auf Touch-Geräten nach einer Berührung deshalb erstmal, um sicherzustellen, dass es sich hier nicht etwa um eine Scroll-Interaktion, ein »double tap« zum Ein- oder Auszoomen, ein »long click« um Text zu selektieren oder ein Context-Menu zu aktivieren, oder einer anderen Browser/OS-spezifischen Geste handelt.

Erst nach einer spürbaren Verzögerung – die je nach Browser bis zu einer halben Sekunde dauern kann – werden die verschiedenen Maus- und der Click-Event dann ausgelöst.

Im Gegensatz zu den simulierten Maus-Events werden Touch-Events ausgelöst, sobald der User mit dem Touchscreen interagiert. Um dies zu verdeutlichen, schauen wir uns kurz einen kleinen Vergleich von Touch- and Click-Events an.

Screenshot Beispiel 3
Beispiel 3 – Touch- und Maus-Event Delay

Wenn wir also unser JavaScript so auslegen, dass es nicht nur auf click, sonder auch direkt auf touchstart reagiert, können wir vor allem bei Web Applikationen die Reaktionszeit unserer Scripts deutlich reduzieren und unsere User-Experience viel geschmeidiger machen.

Wie auch immer ist hier aber Vorsicht geboten. Das folgende JavaScript-Schnipsel, das leider schon oft als »Performance Trick« für Touch angepriesen wird, reagiert – je nach Touch-Unterstützung – entweder auf click oder touchstart Events.

  1. /* Pseudo-code: entweder click oder touch Events abfangen */
  2.  
  3. var event = 'click';
  4.  
  5. if ('ontouchstart' in window) {
  6.   event = 'touchstart';
  7. }
  8.  
  9. foo.addEventListener(event, function() { ... }, false);

Solche Scripts sind zwar gut gemeint, aber sind natürlich im Fall von hybriden Geräten nicht ideal. Deshalb ist es besser, unsere Event-Listener so auszulegen, dass sie auf beide Arten von Events reagieren.

  1. foo.addEventListener('click', function() { ... }, false);
  2. foo.addEventListener('touchstart', function() { ... }, false);

Der Nachteil ist hier, dass unsere Funktion dann in den meisten Fällen doppelt ausgeführt wird, da der Browser ja beide Events abschickt. Wie alle anderen JavaScript Events können wir dieses Standardverhalten vom Browser mittels preventDefault() unterdrücken.

  1. foo.addEventListener('click', function(e) { ... }, false);
  2. foo.addEventListener('touchstart', function(e) {
  3.   ...
  4.   e.preventDefault();
  5. }, false);

Dies funktioniert zwar, hat aber auch so seine Tücken: wenn wir touchstart Events abfangen und mittels preventDefault() jede weiter Bearbeitung unterdrücken, werden auch die normalen Browserfunktionen wie Scrolling, »long click« und Zoom unterbunden. In einigen Fällen ist das wohl ganz praktisch, aber generell sollte diese Methode nur sehr sparsam eingesetzt werden.

Finger-Bewegungen verfolgen

Wie wir schon in Beispiel 1 gesehen haben, wird bei einem Tap auch ein mousemove Event ausgelöst. Man beachte aber, dass es sich hier wirklich nur um einen einzigen mousemove Event handelt: wenn der User mit dem Finger über den Touchscreen fährt, werden nicht – wie bei einer normalen Mausbewegung – weitere Events abgefeuert. Das mag bei den meisten Webseiten kein Problem sein, aber für bestimmte Web Applikationen (nicht zuletzt HTML/JS-basierte Spiele) ist dies natürlich nicht ideal.

Als Beispiel schauen wir uns eine kleine canvas-Spielerei an. Die spezifische Implementierung ist hier nicht so wichtig – was uns in erster Linie interessiert ist, dass das Script auf mousemove reagiert.

Screenshot Beispiel 4
Beispiel 4 – Maus-Tracker
  1. var posX, posY;
  2. ...
  3. function positionHandler(e) {
  4.   posX = e.clientX;
  5.   posY = e.clientY;
  6. }
  7. ...
  8. canvas.addEventListener('mousemove', positionHandler, false );

Zwar ist es möglich, dieses Beispiel auch auf Touch-Geräten zum Laufen zu bringen, aber auf eigentliche Finger-Bewegungen reagiert das ganze nicht – nur die erste Berührung auf dem Touchscreen (die dann die simulierten Maus-Events abfeuert) wird registriert. Um Finger-Bewegungen auf Touch-Geräten zu verfolgen, stehen uns aber touchstart und touchmove Events zur verfügung.

Bevor wir uns diesen neuen Events widmen, müssen wir zuerst etwas weiter ausholen. Gemäß der DOM Level 2 Events Spezifikation wird Funktionen, die auf einen traditionellen Maus-Event als Listener registriert sind, ein MouseEvent-Objekt als Parameter übergeben, in dem unter anderem Attribute wie clientX und clientY vorhanden sind.

  1. /* Pseudo-code: klassische mousemove Funktion */
  2.  
  3. foo.addEventListener('mousemove', function(e) {
  4.   ...
  5.  
  6.   /* WebIDL Definition:
  7.   interface MouseEvent : UIEvent {
  8.     readonly attribute long             screenX;
  9.     readonly attribute long             screenY;
  10.     readonly attribute long             clientX;
  11.     readonly attribute long             clientY;
  12.     readonly attribute boolean          ctrlKey;
  13.     readonly attribute boolean          shiftKey;
  14.     readonly attribute boolean          altKey;
  15.     readonly attribute boolean          metaKey;
  16.     readonly attribute unsigned short   button;
  17.     readonly attribute EventTarget      relatedTarget;
  18.     void               initMouseEvent(...);
  19.   };
  20.   */
  21.  
  22.   /* Koordinaten vom Maus-Pointer relativ zu 'foo' */
  23.   x = e.clientX; y = e.clientY;
  24.   ...
  25. }, false);

Bei Touch-Events sieht das ganze ziemlich ähnlich aus, aber mit einem entscheidenden Unterschied: da Touch-Geräte meistens auch multitouch-fähig sind, bekommen wir in unserer Listener-Funktion zwar ein TouchEvent-Objekt, aber die Koordinaten aller verschiedenen Berührungspunkte sind hier in separaten TouchList-Objekten enthalten.

  1. /* Pseudo-code: touchmove Funktion */
  2.  
  3. foo.addEventListener('touchmove', function(e) {
  4.   ...
  5.  
  6.   /* WebIDL Definition:
  7.   interface TouchEvent : UIEvent {
  8.     readonly attribute TouchList touches;
  9.     readonly attribute TouchList targetTouches;
  10.     readonly attribute TouchList changedTouches;
  11.     readonly attribute boolean   altKey;
  12.     readonly attribute boolean   metaKey;
  13.     readonly attribute boolean   ctrlKey;
  14.     readonly attribute boolean   shiftKey;
  15.   };
  16.   */
  17.  
  18.   ...
  19. }, false);

Wie wir sehen, beinhaltet ein TouchEvent drei verschiedene TouchList Objekte:

touches
alle Touch-Punkte, die momentan auf dem Touchscreen und dem insgesamten Dokument aktiv sind – also auch Punkte, die nicht direkt über unserem 'foo'-Element sind.
targetTouches
nur die Touch-Punkte, die auch auf 'foo' angefangen haben – selbst wenn der Touch-Punkt später außerhalb des 'foo' Elements bewegt wurde und sich momentan nicht mehr direkt über 'foo' befindet.
changedTouches
die Liste aller Touch-Punkte, die sich seit dem letzten Touch-Event verändert haben – falls zum Beispiel in einem Multitouch-Szenario ein Finger von der Touchscreen-Oberfläche entfernt wurde, oder ein extra Finger hinzugekommen ist, werden diese Touch-Punkte hier nochmals explizit aufgezählt.

Diese TouchList-Objekte lassen sich dann wie normale Arrays bearbeiten, um dann – wie schon bei den altebekannten MouseEvent-Objekten – an die individuellen Koordinaten der Berührungspunkte mittels der verschiedenen Attribute wie clientX und clientY zu kommen.

  1. /* Pseudo-code: touchmove Funktion mit TouchList Objekten */
  2.  
  3. foo.addEventListener('touchmove', function(e) {
  4.   ...
  5.  
  6.   /* Nur Touch-Punkte, die auf 'foo' zutreffen */
  7.   var t = e.targetTouches;
  8.  
  9.   for (var i=0; i<t.length; i++) {
  10.  
  11.     /* WebIDL Definition:
  12.     interface Touch {
  13.       readonly attribute long        identifier;
  14.       readonly attribute EventTarget target;
  15.       readonly attribute long        screenX;
  16.       readonly attribute long        screenY;
  17.       readonly attribute long        clientX;
  18.       readonly attribute long        clientY;
  19.       readonly attribute long        pageX;
  20.       readonly attribute long        pageY;
  21.     };
  22.     */
  23.  
  24.     /* Koordinaten vom Touch-Punkt relativ zu 'foo' */
  25.     x = t[i].clientX; y = t[i].clientY;
  26.  
  27.   }
  28.  
  29.   ...
  30. }, false);

Gehen wir nun zurück zu unserem Beispiel. Zuerst modifizieren wir unsere Listener-Funktion so, dass sie sowohl auf Maus-Events als auch auf Touch-Events reagiert. Zuerst wollen wir nur einen einzigen Touch-Punkt verfolgen, deshalb können wir uns erstmal das Abarbeiten der TouchList-Objekte sparen und einfach den ersten Berührungspunkt nehmen:

  1. var posX, posY;
  2. ...
  3. function positionHandler(e) {
  4.   if ((e.clientX)&&(e.clientY)) {
  5.     posX = e.clientX;
  6.     posY = e.clientY;
  7.   } else if (e.targetTouches) {
  8.     posX = e.targetTouches[0].clientX;
  9.     posY = e.targetTouches[0].clientY;
  10.     e.preventDefault();
  11.   }
  12. }
  13. ...
  14. canvas.addEventListener('mousemove',  positionHandler, false );
  15. canvas.addEventListener('touchstart', positionHandler, false );
  16. canvas.addEventListener('touchmove',  positionHandler, false );
Screenshot Beispiel 5
Beispiel 5 – Touch-Tracker

Wenn wir unser Beispiel jetzt erweitern wollen, um auch auf mehrere Touch-Punkte gleichzeitig zu reagieren, müssen wir etwas tiefer in unser Script greifen: anstatt eines einzigen Koordinatenpaares speichern wir einen Array an Punkten (wir klauen uns dafür direkt die targetTouches-TouchList), den wir dann in einer Schleife durchlaufen.

  1. var points = [];
  2. ...
  3. function positionHandler(e) {
  4.   if ((e.clientX)&&(e.clientY)) {
  5.     points[0] = e;
  6.   } else if (e.targetTouches) {
  7.     points = e.targetTouches;
  8.     e.preventDefault();
  9.   }
  10. }
  11. ...
  12. function loop() {
  13.   ...
  14.   for (var i = 0; i<points.length; i++) {
  15.     /* Kreis zeichnen */
  16.     ...
  17.   }
  18.   ...
  19. }
Screenshot Beispiel 6
Beispiel 6 – Multi-Touch-Tracker

Noch eine kurze Anmerkung zur Performance: wie es auch bei mousemove Events und traditionellen Maus-Interaktionen der Fall ist, werden bei einer Fingerbewegung eine recht hohe Anzahl an touchmove Events abgesandt. Daher ist es – vor allem mit Hinsicht auf ältere, weniger performante Touch-Geräte – ratsam, unsere touchmove Funktion nicht mit prozessorlastigen Operationen zu überladen. In unserem Beispiel speichert die Funktion nur die momentan aktiven Touch-Punkte – der Code, der diese Koordinaten dann umsetzt, wird separat (mittels setTimeOut) aufgerufen.

Fazit

Mittels Touch-Events ist es relativ einfach, Webseiten und Applikationen so zu gestalten, dass sie auch auf Touchscreens optimal bedienbar sind. In diesem Artikel haben wir nun wirklich nur einen Blick auf die Grundlagen geworfen, um die Reaktionszeit unserer click-Skripte zu verbessern und um an die eigentlichen Touch-Punkte zu gelangen.

Es ist natürlich durchaus möglich, diese Informationen noch weiter zu bearbeiten und Apps noch mit nativ-ähnlichen Swipe- oder Multitouch-Gesten nachzurüsten, wie zum Beispiel in der Picture Organizer Demo. Die Details hierzu würden aber den Rahmen dieser kleinen Einführung sprengen.

Weiterführende Links

Kommentare

Martin
am 18.12.2012 - 18:56

Guter Artikel über die Verwendung von Multi-Touch im Browser. Firefox auf Win7 unterstützt meines Wissens jedoch immer noch nicht die W3C Touch Events, sondern die veralteten MozTouchEvents. Eine Library die diese cross-browser Probleme ausgleicht ist jQMultiTouch (http://dev.globis.ethz.ch/jqmultitouch/). jQMultiTouch bietet zudem eine sehr flexible Multi-Touch Gestenerkennung! [Disclaimer: ich habe Teile der Library mitentwickelt]

Permanenter Link

Die Kommentare sind geschlossen.