Graceful Degradation

vorige Präsentation: Unobstrusive Javascript | zurück zum Buch-Kapitel [esc] | Nächste Präsentation Besondere Javascript-Schreibwesen in jQuery

Die Aufgabenstellung: ein einer langen Webseite sind mehrere Anker-Punkte mit <a name=...> gesetzt, über ein Navigationsmenü soll man diese erreichen können. Diese Seite zeigt ein funktionierendes Beispiel (letzte Version).

Version 1

Wir beginnen mit völlig problemlosem HTML

Html Code webseite mit Anker-Punkten, fixe Navigation

<div id="navigation">
  <a href="#s0">Thema 0</a> 
  <a href="#s1">Thema 1</a> 
  ...
</div>  
<section id="s0">
  <h2>Thema 0</h1> 
  <p>bla bla ...</p>
</section>
<section id="s1">
  <h2>Thema 1</h1> 
  <p>bla bla ...</p>
</section>
...
#navigation {
  position: fixed;
  z-index: 10;
  bottom: -1px;
}









Version 2

In einem Versuch die Seite zu verbessern ersetzen wir nun die Links durch den Aufruf einer Javascript-Funktion:

Html Code Version 2

<div id="navigation">
  <a onClick="scrollToMe('#s0')">Thema 0</a> 
  <a onClick="scrollToMe('#s1')">Thema 1</a> 
  ...
</div>    

Die Javsacript-Funktion verwendet jQuery und die übergebene ID um den Ziel des Links ausfindig zu machen, und dann die jQuery Methode offset um die Position des Ziels im Dokument zu berechnen.

Mit der jQuery-Methode animate wird dann eine Animation erzeugt: binnen 800 Millisekunden wird die Seite durch setzen von scrollTop von der aktuellen Scrollposition in die Scrollposition gebracht, die das Ziel ganz oben im Fenster anzeigt.

Mit return false wird die “normale” Funktion des Links deaktiviert.

Javascript Code Javascript für Version 1

function scrollToMe(id) {
  var top  = $(id).offset().top;
  $('body').animate({ scrollTop: top }, 800);
  return false;
}

Diese Version ist kein Beispiel für gutes Javascript: in manchen Browsern funktioniert das Scrollen der Seite mit scrollTop nicht.

Mit dieser Version haben wir

Wir haben dabei beide Prinzipien gebrochen

Version 3

Im nächsten Versuch werden wir jQuery verwenden, um unobstrusive zu programmieren:

Die Navigation wird wieder zurück-gestellt auf normale HTML-Links:

Html Code Version 3

<div id="navigation">
  <a href="#s0">Thema 0</a>
  <a href="#s1">Thema 1</a>
  ...
</div>

Die Funktion scrollToMe wird als Eventhandler implementiert: sie erwartet ein Event als Argument und die angeklickte Node in der Variablen this. Ausserdem verwendet die Funktion die jQuery-Methode preventDefault um das “normale” Verhalten des Links zu unterbinden.

In der letzten Zeile wird an alle Links in der Navigation die Funktion scrollToMe als Eventhandler für click angefügt.

Javascript Code Version 3

$(document).ready( function () {

  function scrollToMe(event) {
    var link = $(this).attr('href'),
        top  = $(link).offset().top;
    $('body').animate({
      scrollTop: top
    }, 800);
    event.preventDefault();
  }

  $('#navigation a').on('click', scrollToMe);
});

Diese Variante funktioniert schon besser:

Wir haben damit schon ein Prinzip eingehalten, und sind beim anderen Prinzipien auf halben Weg

Version 4

Im nächsten Schritt werden wir sicher stellen, dass die Animation nur in solchen Browsern verwendet wird, wo sie auch funktioniert.

Achtung: hier gibt es einen falschen und einen richtigen Ansatz:

  1. Browser Detection: Unterscheidung nach Namen, Versionsnummer, Betriebssystem des Browsers
  2. Feature Detection: Unterscheidung nach genau der Fähigkeit, die ich verwenden will

Die erste Variante funktioniert nicht: die Selbstoffenbarung der Browser kann falsch sein, ich kenne nicht alle Browser und ihre Fähigkeiten. Siehe auch Zakas(2009): Feature detection is not browser detection. In NCZOnline.

Die Funktion scrollToMe bleibt unverändert. Wir testen ob die Funktion scrollTop wirklich den Wert von scrollTop verändern kann. Wenn das funktioniert wird die globale Variable can_scroll auf true gesetzt, andernfalls auf false.

Javascript Code Version 4

// try out scrollTop,
// set global Flag can_scroll
var old_scrolltop = $('body').scrollTop();
$('body').scrollTop(10);
window.can_scroll = ( $('body').scrollTop() > 0 );
$('body').scrollTop(old_scrolltop);

if ( window.can_scroll ) $('#navigation a').on('click', scrollToMe);

Diese Herangehensweise - Feature Detection, dann Flags setzen, die im weiteren Code verwendet werden können - wird von der Library modernizr für eine lange Liste von Features angeboten.

Nebenbemerkung: In ganz seltenen Fällen muss man doch Browser Detection machen. Eine Gute Library dafür ist HTML5 please. Damit kann man eine Liste von Features angeben die erfüllt sein müssen damit die Seite funktioniert. Ist das nicht der Fall, dann wird eine entsprechende Meldung angezeigt

HTML5 please Fehlermeldung

Diese Variante behebt das Problem mit nicht-funktionierenden Javascript-Browsern:

Es bleibt aber noch in Problem:

Version 5

In der klassischen Version ändert sich beim navigieren zwischen den Ankern jeweils die URL im Browser. Wenn ich ein Ziel annavigiere, und dann die URL kopiere um einen Link zu setzen / mir eine Bookmark setze, dann verweist die URL die ich verwende wirklich wieder genauf auf das Ziel.

Dieses Verhalten ist erstrebenswert, wird aber von der “animierten” Version derzeit nicht geliefert.

Diese “Navigierbarkeit” ist auch ein klassisches Problem von AJAX-Applikationen, die Lösung die wir hier entwickeln funktioniert auch dort:

In die Funktion scrollToMe wird eine Zeile eingefügt. Mit dem History-Objekt kann man den Browser von Javascript aus “navigieren”: mit history.back() zum Beispiel einen Schritt zurück gehen.

Mit history.pushState() kann man zu einer neuen Seite navigieren, sie wird dabei an die History angefügt - das ist das “normale” Verhalten des Browsers.

Eine Alternative ist history.replaceState() - dabei wird die aktuelle Seite ersetzt, die Browser-History wird nicht länger.

Beide Methoden erwarten drei Argumente - ein Objekt und zwei Strings - aber nur das letzte Argument wird derzeit benutzt. Es ist ein String mit der URL die geladen werden soll.

Javascript Code Version 4

function scrollToMe(event) {
  var link = $(this).attr('href');
  ...
  window.history.pushState( {}, "Thema " + link, link);
}

Mit dieser Variante haben wir für die Javascript-Browser alle Funktionalität der einfachen HTML-Version wiederhergestellt. Und zusätzlich gibt es eventuell noch eine schöne Animation.

Damit sind beide Prinzipien voll erfüllt:

History auch in HTML4 Browsern verwenden: https://github.com/balupton/History.js/

Graceful Degradation

vorige Präsentation: Unobstrusive Javascript | zurück zum Buch-Kapitel [esc] | Nächste Präsentation Besondere Javascript-Schreibwesen in jQuery

/

#