Daniel Hundt - Erfahrung Technologien im Frontend

zurück zur Technologie-Übersicht

Wischen (Im)possible: Bildergalerie mit Swipe-Steuerung

Inhalt

Bei der Gestaltung einer (mobilen) Seite steht bei mir insbesondere die Usability / Bedienbarkeit im Vordergrund. Auf mobilen Geräten umfasst dies im speziellen die spielerische Eingabe per Touch-Gesten.

Als ein abgegrenzter Bereich kann dabei die Bildergalerie stehen, durch die man mittels entsprechenden "Wischens" (Swipe) navigieren kann. Leider ist die Umsetzung des ganzen (noch?) nicht sonderlich trivial, wenn man folgende Anforderungen erfüllen soll:

Problemstellungen

  1. Die Swipe-Geste soll erkannt werden.
  2. Das Bild soll beim Swipen "am Finger kleben bleiben".
  3. Mit einem "Schwung" sollen gleich mehrere Bilder übersprungen werden ("beschleunigt").
  4. Ist die Bildergalerie einmal in Schwung versetzt, soll diese aber auch jederzeit anhaltbar sein.
  5. Die Swipe-Bewegung soll nicht zwischen zwei Bildern enden, sondern an definierten Punkten einschnappen.
  6. Die Bildergalerie soll endlos sein. Bin ich beim letzten Bild und swipe weiter, soll wieder das erste Bild erscheinen.
  7. Die Steuerung via JavaScript sollte generell möglich und auch erweiterbar sein.

Bei der Analyse des Problems bin ich auf die iScroll-Bibliothek gestoßen. Leider hat diese JavaScript-Lib (damals?) nicht alle Anforderungen erfüllt. Dennoch war sie eine gute Quelle, um auf den bereits gewonnenen Erkenntnissen des Autors (insbesondere zum beschleunigten Swipe) aufzubauen.

Erst durch die Teilung des "großen Problems" in mehrere kleinere, konnte ein strukturiertes Vorgehen stattfinden. Die einzelnen Probleme wurden nacheinander angegangen und die Lösungen am Ende in einer voll funktionsfähigen Bildergalerie kombiniert.

Swipe-Geste erkennen

Aller Anfang ist schwer ... und so wurde das Event-Handling anhand erster Prototypen / Tech-Demos auf unterschiedlichen Endgeräten unterschiedlicher Hersteller erforscht.
Dabei hat sich gezeigt, dass es doch die ein oder andere Besonderheit gibt. Generell gibt es folgende drei Touch-Events, die von Interesse sind:

Touch-Events

  • touchStart - wenn das Element berührt wird (analog zu mouseDown)
  • touchMove - wenn auf dem Element "gewischt" wird (analog zu mouseMove)
  • touchEnd - wenn das Element nicht länger berührt wird (analog zu mouseUp)

Dabei ist insbesondere zu beachten, dass - im Gegensatz zur Maus - mehrere Berührungen und Bewegungen parallel stattfinden können ("multi touch"). Einige Geräte unterstützen bis zu zehn oder mehr parallele Touch-Eingaben. Außerdem gibt es (mal wieder -.-') Abhängigkeiten zum Browser zu beachten:

Abhängigkeiten: Browser und co.

  • Ansprache der Touch-Daten im Event-Objekt
  • Absolute oder relative Positionsangaben (zu welchem Bezugspunkt?)
  • Abhängigkeit zum Zoom-Level und der Bildschirmauflösung (dpi)
  • Abhängigkeit, ob Zoom erlaubt ist oder nicht
  • Event muss ggf. "konsumiert" werden (nicht an System weitergeben), da sonst keine weiteren Events ausgelöst werden
  • Umgang mit Verlassen des DOM-Objekt-Bereichs während der Touch-Bewegung

Wann ist eine Touch-Bewegung ein Swipe?

Generell gab es folgende Events zu unterscheiden:

  • Klick - kurzer Moment zwischen touchStart und touchEnd, geringe Bewegung in touchMove
  • langer Klick - geringe Bewegung in touchMove
  • Zoom - Geste mit zwei Fingern
  • Scrollen - stärkere vertikale Bewegung (hoch/runter) als horizontal (links/rechts)

Die Erkennung selbst kann sicherlich noch (erheblich) verbessert werden. Das Ergebnis kann sich allerdings schon sehen lassen und sollte in der Mehrheit der Fälle ausreichend sein (Verhältnismäßigkeit zur investierten Zeit).

Nur: Von der Erkennung der Events alleine, bewegt sich noch nichts!
Im ersten Wurf konnte dadurch allerdings bereits eine Aktion "jetzt umblättern" getriggert werden.

Klebendes Swipe

Ziel ist es ja, dass man das DOM-Element mit Hilfe der Touch-Eingabe "in Echtzeit" bewegt. D.h. das Element soll am Finger kleben bleiben und mitwandern. Dazu wird bei jedem touchMove-Event die Position des Elements aktualisiert. Dazu gibt es mehrere Möglichkeiten:

  • margin
  • Positionsangaben (top, right, bottom, left)
  • transform:translate
  • transform:translate3d

Bei den Positionsangaben (top, right, bottom, left) muss der Kontext / Bezugspunkt beachtet werden (position:absolute/relative/fixed). Margin hat ggf. Auswirkungen auf umfließenden Text.

Performance-Optimierung

Ich bilde mir ein gelesen zu haben, dass translate3d Hardware-beschleunigt abgebildet wird und damit flüssiger läuft. Ggf. trifft dies aber (mittlerweile) auch auf translate und die beiden anderen Varianten zu.

In komplexen Strukturen, mit großen Objekten und auf langsamen Geräten kann es vorkommen, dass die Touch-Events schneller eintreffen, als diese abgearbeitet werden können (Rendern der Seite). Hier hat es sich bewährt, eine asynchrone Abarbeitung zu nutzen (z.B. setTimeout) um ein (größeres) Stocken zu vermeiden. Übergänge zwischen den einzelnen Touch-Events bzw. deren Umsetzung können zusätzlich via CSS 3-Transition geglättet werden.

Wichtig ist außerdem, den Kontext bei der Bewegung zu beachten. Ggf. ändert sich die Position im Touch-Event relativ zur Positionsänderung des DOM-Elements.

Auch ganz wichtig: Die Grenzen der Bewegung beachten. Sonst wischt man über das Ende des Bereiches hinaus!

Beschleunigter Swipe "mit Schwung"

Den Effekt kennt man bereits aus zahlreichen Apps: Man Wischt kurz und beschleunigt dadurch das "darunter liegende". Durch Trägheit bleibt das Element (trotz gelöstem Finger) in Bewegung, wird nur durch eine simulierte Reibung gebremst und kommt schließlich irgendwann zum Stehen.

Hier konnte mir ein Blick in die iScroll-Bibliothek weiterhelfen. Anhand der Formeln für Weg, Beschleunigung und Geschwindigkeit konnte mit Hilfe geeigneter Werte das gewünschte Verhalten simuliert werden:

Ohne Mathematik / Physik wird das nichts!

Weg = 0.5 x Beschleunigung x Zeit² + Anfangsgeschwindigkeit x Zeit
Geschwindigkeit = Beschleunigung x Zeit + Anfangsgeschwindigkeit
(bzw. für den initialen Schwung: )
Geschwindigkeit = Weg / Zeit

Anfangsgeschwindigkeit, Weg oder Zeit stehen fest. Der Wert für die "Entschleunigung" wird frei gewählt, so dass er sich "gut anfühlt". Als Inspirationsquelle kann auch wieder die iScroll-Bibliothek genutzt werden.

Um eine flüssige Darstellung zu gewährleisten, sollte eine CSS3 Transition vom Typ ease-out mit der berechneten Zeit verwendet werden. Der Endpunkt-Animation berechnet sie ebenfalls aus den oben genannten Formeln. Um ein abruptes Anhalten an der Grenze des DOM-Elements zu vermeiden, sollte die Anfangsgeschwindigkeit so limitiert werden, dass die Grenze passend zur Entschleunigung erreicht wird.

Stoppen des beschleunigten Swipes

Tritt während des "Schwungs" ein touchStart-Event auf, muss die aktuelle Position innerhalb der Animation anhand der oben genannten Formeln berechnet werden. Steht die Position fest, kann die Animation beendet und die ermittelte Position gesetzt werden.

Einrasten an definierten Punkten

Beim "beschleunigten Swipe" kann über die oben genannten Formeln der Endpunkt der Animation berechnet werden. Zu dem berechneten Punkt kann der nächstgelegene Einrast-Punkt (in Abhängigkeit der Swipe-Richtung!) ermittelt werden. Ist der Einrastpunkt ermittelt, kann die Anfangsgeschwindigkeit entsprechend manipuliert werden um diesen Punkt gleichmäßig zu erreichen.

Bei geringer Anfangsgeschwindigkeit (wenig bzw. kein Schwung/klebend) kann auch mit einer Mindestzeit (z.B. 600ms) gearbeitet werden um den nächsten Einrastpunkt zu erreichen.

Endlose Bildergalerie

Zur endlosen Bildergalerie werde ich noch einen eigenen Artikel schreiben.

In aller Kürze: die Galerie wird drei mal hintereinander eingefügt. Man startet immer im mittleren Segment und kann bis zum ersten oder letzten Segment sliden. Am Ende jedes Slides wird die Position wieder auf das mittlere Segment zurückgesetzt. Kann immer nur ein Bild geswipet werden, so ist auch eine einfachere Lösung möglich.

Steuerung via JavaScript

Da die Touch-Events und die ganze Berechnung bereits im JavaScript abläuft, können an dieser Stelle auch Funktionen zum Eingriff von außen integriert werden. An bestimmten Stellen können Callback-Funktionen ausgelöst werden (eigenes Event-Handling) oder die Bewegung kann (ohne Touch-Events) simuliert werden.

In Bezug auf die Bildergalerie konnte so folgendes realisiert werden bzw. ist denkbar:

  • Realisirung der "endlosen Bildergalerie": Rücksprung in mittleres Segment
  • die Navigationsleiste ist synchronisiert zur aktuellen Position
  • per Klick in die Navigationsleiste kann das entsprechende Bild angesprungen werden
  • automatisches Weiterschalten nach einer gewissen Zeit
  • Statistik über angesehene Bilder