Touchbedienung in der Webapp

Bildergalerie, iPad Version

Bildergalerie bei ZEIT ONLINE, Ansicht auf dem iPad

Die Touchbedienung ist einer der großen Unterschied zwischen Tablet- und Desktopcomputer. Dieses Bedienkonzept, wie es der Nutzer inzwischen auch schon aus den sog. nativen Apps gelernt hat, so sinnvoll wie möglich umzusetzen und zu nutzen, sollte auf jeden Fall Teilaufgabe einer HTML5-App sein. Leider stehen der App im Browser nicht alle Schnitsstellen und Fähigkeiten zur Verfügung, die bei der Programmierung einer nativen App genutzt werden können. Darauf kommt es aber allein auch nicht an, wie gesagt, es geht eben auch und vordringlich um das Bedienkonzepte.

Als alten Hut muss man zum Beispiel die Bedienung mit Knöpfen sehen, denn Buttons sind eben typischerweise Klickziele für den Mauszeiger und weniger für den Finger. Obwohl ein Button natürlich auch in der Touchbedienung funktionieren kann; dann muss er aber groß und gut zu treffen sein, und auch noch erkennbar, wenn der Nutzer den Finger darauf legt. Trotzdem, ein Button zum Vor- und Zurückblättern in einer Bildergalerie, ist nicht wirklich tabletgemäß.

Um es kurz zu machen: in einer Bildergalerie wie dieser hier will der Nutzer sliden – mit dem Finger das Bild aus dem Viewport schieben – und nicht Knöpfe drücken. In der Tabletversion haben wir das im Ansatz umgesetzt. Nutzt man die Seite mit dem iPad, kann man mit dem Finger von einem Bild zum nächsten navigieren. Da ich danach gefragt wurde, will ich kurz erklären wie das funktioniert (und danach auch noch dazu kommen, was noch verbessert werden muss).

Zunächst mal haben wir das Konzept der meisten Bildergalerien auf ZEIT ONLINE übernommen. Die bestehen zum Ladenzeitpunkt nur aus ihrem ersten Bild, die restlichen Bilder werden nachgeladen und versteckt horizontal rechts vom ersten Bild aufgereiht. Klickt man einen der Buttons wird der Container mit den Bildern einfach immer um Bildbreiter nach links oder rechts bewegt.

Die Bedienung durch die Buttons haben wir nur durch die Touchbedienung erweitert. Vielleicht sei kurz erwähnt wie Javascript einen solchen Touchevent sieht: es gibt die vier Events touchstart, touchmove, touchend und touchcancel. Die ersten drei laufen immer nacheinander ab, wobei touchmove solange wiederholt wird, wie man mit dem Finger über die Touchoberfläche slidet. touchcancel tritt auf, wenn eine Touchaktion abgebrochen wird, bspw. wenn man mit dem Finger über den Bildschirm hinaus geht. Die Events haben als Eigenschaften (man kennt das von Mouseevents) u.a. die Bildschirmkoordinaten, wo sie ausgelöst wurden.

Damit lässt sich also leicht erkennen, ob zwischen touchstart und touchend eine Bewegung, also ein Swipe stattgefunden hat. Hier mein seht vereinfachtes, beispielhaftes jQuery-Plugin dazu (nur als Beispiel, nicht zur Implementation gedacht oder empfohlen):

[js]
$.fn.addSomeTouch = function (options) {
var xmoved = 0, xstart = 0;
return this.each( function() {
// touchstart event
$( "img", this ).bind( "touchstart", function () {
// jeder Finger ist ein Feld im Array
// hier: 1-Finger-Geste
if( event.touches.length == 1 ) {
// Startpunkt merken
xstart = event.touches[0].pageX;
}
});

// touchmove event
$( "img", this ).bind( "touchmove", function () {
// Bewegungsentfernung messen
xmoved = event.touches[0].pageX – xstart;
});

// touchend event
$( "img", this ).bind( "touchend", function () {
// Limit für Verzögerung
if( xmoved >= 50 ) {
alert( "I was touched and will move left." );
} else if( xmoved <= -50 ) {
alert( "I was touched and will move right." );
}
// Reset
xstart = 0;
xmoved = 0;
});

$( "img", this ).bind( "touchcancel" , function() {
// bei Abbruch zurücksetzen
xstart = 0;
xmoved = 0;
});

});
};
[/js]

Wie gesagt, das ist kein Code für den Produktionseinsatz.

Und was stimmt jetzt nicht? Nun, im Moment setze ich die Fingerbewgung nur als Schalterersatz ein. D.h., die Reaktion ist dieselbe, als würde man auf den Links- oder Rechtsknopf drücken, die ehrlicherweise ja auch noch da sind. Da bleibt ja noch Raum für die Weiterentwicklung, denn so richtig responsiv ist das ja noch nicht.

Und am Schluss noch ein schöner Link dazu: bei Touching and Gesturing on the iPhone, gibt’s viele Grundlagen nachzulesen und das meiste gilt auch für’s iPad.

Javascript schöner und schneller machen

Javascript ist, angesichts seiner etwas holprigen Entstehungsgeschichte eine eigentlich recht elegante Scriptsprache. Es krankt jedoch daran, dass in Javascript im Grunde alles geht und alles gemacht wird und jedesmal anders. Will man mit Kollegen zusammen an einem Javascriptprojekt arbeiten, ist der eigene Dialekt ebenso hinderlich, wie mangelnde Sauberkeit beim Codeschreiben. Beachtet man aber neben einem guten Stil in Javascript einige elementare Regeln, kann man seine Scripte dadurch auch noch merklich schneller und kompakter machen.

Coding Guidelines

In Sachen Coding Guidelines gilt auf jeden Fall: jede Regel ist besser als keine Regel. In Javascript kann man viele böse Dinge schreiben, die kein Mensch versteht, die aber trotzdem hervorragend funktionieren. Die Sprache lässt einfach vieles zu, was trotzdem schlechter Stil ist. Vor kurzem haben sich die Entwickler von jQuery einen Styleguide verpasst, die JQuery Core Style Guidelines, das ist schon eine wunderbare Grundlage. Einfach copy & pasten.

Wichtig ist dabei auch, die Verwendung von JS Lint (JSLint will hurt your feelings.). Mit diesem Tool kann man die übelsten Stylefehler vermeiden und die Einhaltung des Styleguides automatisiert überprüfen. Die Einführung dazu ist mehr als lesenswert. Besonders praktisch für Nutzer von Textmate (Lieblingseditor) ist das JS Lint Bundle.

Codeverbesserung: erster Durchgang

Sich aufzuraffen, seinen Code zu überarbeiten, ist nicht leicht. Aber: der Wille zur Verbesserung und das Wissen, dass der erste Aufschlag meist nicht optimal ist, trennt den Programmieren vom Scriptingguy, oder so. Hier ein paar schnelle und einfache Schritte, die Javascript schon deutlich verbessern. Federice Galassi hat darüber eine wunderbare Präsentation gemacht. Die Maßnahmen führen aber nicht nur in Richtung unobtrusiveness, sondern sind auch geeignet, Javascript schneller und wartbarer zu machen. Kurz zusammengefasst:

  1. Entferne alles Inline-Javascript aus dem HTML-Code.

    Also <script> Code…</script> raus aus den Seiten und in eigene Dateien (besser eine eintige) packen. Diese dann mit <script src="datei.js"></script> aufrufen. Und das am besten am Ende der Seite, direkt vor </body>.

  2. Alle Inlineevents aus dem HTML entfernen

    Weg mit <a class="klick" href="#" onclick="foo();">Klicken Sie hier</a>. Das kann besser machen. Mit jQuery beispielsweise: $("a.klick" ).bind("click", function() { foo(); });, jedenfalls aber in der externen Javascriptdatei.

  3. Javascript-Pseudolinks entfernen

    Mit Todesstrafe wird der uralte Javascript-Pseudolink bestraft! Sowas geht gar nicht: <a href="javascript:foo()">Klicken Sie hier</a>

  4. CSS-Code aus dem Javascript entfernen.

    Innerhalb des Javascriptes sollte kein CSS verwendet werden (Trennung von Präsentation und Programmierlogik). D.h. sowohl Zuweisungen wie $("a" ).css("background","#ff0000" ); oder auch document.getElementById("id").style.color("#ff0000"); und ähnliches schreiben teilweise lange unüberschaubare style-Attribute in den HTML-Code und produzieren DOM-Zugriffe. Stattdessen schreibt man die CSS-Anweisungen in eine CSS-Datei

    [css]
    // bspw. base.css
    a.rot {
    background: #ff0000;
    }
    [/css]

    und nutzt im Javascript

    [js]
    $("a").addClass("rot"); // Farbe an
    $("a").removeClass("rot"); // Farbe aus, oder gleich:
    $("a").toggleClass("rot"); // je nach dem
    [/js]

    CSS bewegt sich wesentlich schneller und gewandter durch das DOM als Javascript.

  5. Businesslogik aus dem Javascript entfernen (Client-Server-Anwendungen).

    Bei Client-Server-Anwendungen kann es einen entscheidenen Geschwindigkeitsvorteil bedeuten, komplizierte Berechnungen nicht auf dem Client (also mit Javascript) sondern auf dem Server auszuführen. Wenn man sich also schon Daten vom Server holt, dann sollte man vermeiden mit diesen Daten Berechnungen auf dem Client auszuführen. Ein Beispiel:

    [js]
    // Schlecht!
    $.get("action.php", function ( data ) {
    if ( data ) {
    if ( data.kontostand > data.dispokredit ) {
    alert("Konto überzogen" );
    }
    }
    });

    // Besser!
    $.get("action.php?dispoberechnung", {konto:"123456"}, function ( data ) {
    if ( data.dispo === false ) {
    alert("Konto überzogen" );
    }
    }
    [/js]

  6. DOM-Operationen auf ein Minimum beschränken!

    Nicolas Zakas formuliert es in High Performance JavaScript (Build Faster Web Application Interfaces) so:

    An excellent analogy is to think of DOM as a piece of land and JavaScript (meaning ECMAScript) as another piece of land, both connected with a toll bridge (see John Hrvatin, Microsoft, MIX09, http://videos.visitmix.com/MIX09/T53F ). Every time your ECMAScript needs access to the DOM, you have to cross this bridge and pay the performance toll fee. The more you work with the DOM, the more you pay. So the general recommendation is to cross that bridge as few times as possible and strive to stay in ECMAScript land.

    Daraus ergibt sich eine klare Anweisung: greife sowenig wie es eben geht auf das DOM zu.

    Ein Domzugriff ist dann gegeben, wenn ein DOM-Element erschafft, ins DOM einhängt, ein Element im DOM verschiebt, Attribute hinzufügt und so weiter und so fort. Am allerschlimmsten sind die sogenannte HTML-Coolections und das iterieren hierauf.

    [js]
    // ein ganz böses Beispiel!
    function innerHTMLLoop () {
    for ( var i = 0; i < 15000; i++ ) {
    document.getElementById("meinLink" ).innerHTML +="a";
    }
    }
    [/js]

    Hier erleben wir schlappen 15000 DOM-Aufrufe der übelsten Art. Stattdessen vermeidet man solange wie möglich den Zugriff aufs DOM:
    [js]
    // dann besser so
    function innerHTMLLoop2 () {
    var content ="";
    for ( var i = 0; i < 15000; i++ ) {
    content +="a";
    }
    document.getElementById("meinLink" ).innerHTML += content;
    }
    [/js]

    Goldene Regel: pro Funktion sollte es nur einen Zugriff auf den DOM geben. So kann es beispielsweise Sinn machen, sehr lange auf Strings mit HTML zu arbeiten und erst am Ende alles zusammen per $( var ).appendTo( 'body' ); ins DOM zu hängen. Ausser in Chrome und Safari ist es sogar noch schneller innerHTML zu benutzen, in jQuery $("#meinKram" ).html( var );. Hier scheiden sich allerdings die Geister, weil innerHTML nicht standardkonform ist, wohl aber von jedem Browser unterstützt wird.

  7. Große HTML-Chunks ggf. durch Templates ersetzen.

    Wegen der schon oft angesprochenen Trennung von Content und Präsentationslogik ist zu überlegen, ob man für große HTML-Stücke die man in den Code einspeisen muss, vielleicht besser HTML-Templates verwendet, den Code also in externe Dateien auslagert und nachlädt (auf welche Weise auch immer). Als Vorstufe dazu und Kompromiss kann man auch bis mittelgroße Codeschnipsel im verborgenen Teilen des HTML-Dokuments unterbringen und sich dann per .clone() oder wieder .html() einlesen und wiederverwenden.

    Die Verwendung eines Templatesystems setzt aber bspw. wieder ein eingenes Plugin voraus, Schnipsel im HTML verursachen beim Laden wieder DOM-Zugriffe. Hier muss man genau abwiegen, was der Performance hier zuträglich ist, sicherlich auch in Abhängigkeit vom der Größe des HTML-Codes der benötigt wird.

  8. Keine/wenig globalen Variablen nutzen!

    Variablen die ausserhalb von Funktionen definiert werden, sind, auch wenn sie mit var geschrieben werden, globale Variablen, die im window-Namensraum gespeichert werden. Es besteht die Gefahr, dass diese an anderer Stelle ungewollt überschrieben werden, außerdem ist der Zugriff auf window langsam. Stattdessen speichert man Variablen aus dem Window-Namensraum in lokalen Variablen zwischen, auf die der Zugriff wesentlich schneller erfolgt.

Fortsetzung folgt

Dieser 8-Punkte-Plan ist aber nur ein Einstieg. Wenn man ein Programm so optimiert hat, kann man praktisch direkt wieder von vorne anfangen und weitere Verbesserungen einbringen. Das wird dann das Thema eines weiteren Artikels hier.

Mit Opera einen HTML5-Videoplayer bauen

in den Browser eingebauten Players abzuspielen. Dieser Fakt ist ja nun inzwischen hinreichend bekannt. Auch schon gehört hat man davon, dass man diesen Videoplayer selbst gestalten kann, also eigene Buttons, Seekbar und Controls hinzufügen kann. Aber wie genau macht man das?

Bei Opera gibt es genau zu dieser Frage nun ein ausführliches Tutorial: Building a custom HTML5 video player with CSS3 and jQuery. Ganz großes Kino.

Sprite up the web

Unter dem klanghaften Motto »Sprite up the web« finden wir das Spritely jQuery Plugin, das angetreten ist, die Webseitenwelt mit Spriteanimation ein wenig hübscher zu machen. Und wirklich, dass was wir auf der Seite sehen, und auch einige der Beispiele aus der Early Adopter Gallery, machen wirklich Spass.

Das Plugin stellt zwei Methoden zur Verügung, und zwar sprite(), mit der Elemente aus Einzelbildern animiert werden könne, und pan(), mit der (mehrere) Endloshintergrundbilder animiert verschoben werden können. Das alles soll in den Browsern IE6+, Firefox, Safari, Chrome und Opera funktionieren und außerdem einen angepassten Modus für iPhone, Android und iPad haben. Bitte schön: alles natürlich ohne Flash, Silverlight oder sonstigen Rotz.

EDerzeit liegt Spritely in Version 0.2 vor, es ist also noch Entwicklungsluft nach oben, aber das sieht alles schon ganz brauchbar aus. Bitte testen.

Geolocation im Browser erkennen

Heute wollte mir jemand weissmachen, zum Nutzen der Geolocation-Informationen des iPhones bräuchte man eine App. Quatsch.

Seit iPhone 3.0 geht im Safari:

navigator.geolocation.getCurrentPosition(foundLocation(position){^
	var lat = position.coords.latitude;
	var long = position.coords.longitude;
	alert('Found location: ' + lat + ', ' + long);
}, noLocation(){
	alert('Could not find location');
});

Weil ich die veraltete Information aber auch gerade nochmal in einem Buch gelesen habe und dort beinahe ausschließlich auf jQuery gesetzt wird: es gibt natürlich auch ein entsprechendes jQuery-Plugin: jquery-geolocation. Die ist kompatibel zum Safari/iPhone ab Version 3.0 und Firefox ab 3.5.

$.geolocation.find(function(location){
   alert(location.latitude+", "+location.longitude);
}, function(){
   alert("Your device doesn't support jquery.geolocation.js");
});

jQuery 1.4: Funktionen übergeben beim Setting

Was offenbar (also an mir ist das irgendwie vorrüber gegangen) für .attr() schon länger funktioniert, wurde beim neuen jQuery 1.4 nun für alle Settingfunktion möglich gemacht: das Übergeben von Funktionen zum Setzen eines Wertes. Namentlich funktioniert das nun also bei: .css(), .attr(), .val(), .html(), .text(), .append(), .prepend(), .before(), .after(), .replaceWith(), .wrap(), .wrapInner(), .offset(), .addClass(), .removeClass() und .toggleClass().

jQuery 1.4: Funktionen übergeben beim Setting weiterlesen

jQuery 1.4 und die neue API-Dokumentation

Zusammen mit der Veröffentlichung von jQuery 1.4 wurde die jQuery-Dokumentation neu geschrieben und unter neue Adresse http://api.jquery.com/ veröffentlicht. Dort findet man ab sofort eine Liste aller jQuery-Methoden, Selektoren und Eigenschaften. Diese widerum sind dann, jeweils unter einem eigenen URI, wie bspw. http://api.jquery.com/html oder http://api.jquery.com/bind ausgiebig dokumentiert. Dort ist auch die Versionsgeschichte bis jQuery 1.0 zurück zu finden.

jQuery 1.4 und die neue API-Dokumentation weiterlesen