Freitag,
12. Februar 2010

Skiplinks gehören zu den grundlegenden Navigationshilfen, welche die Zugänglichkeit komplexer Webseiten verbessern können, indem sie es dem Nutzer erlauben, wichtige Elemente einer Webseite schnell und ohne Umwege zu erreichen, z.B. die Hauptnavigation, den Inhaltsbereich oder ein Formular. Aufgrund meiner Erfahrungen bei der Arbeit an und mit YAML gibt’s hier nun einen kleinen Best Practice Beitrag zum Thema.

Anforderungen

Zunächst wären noch einmal die Anforderungen an Skiplinks zu definieren. Die WCAG 2.0 gibt unter G1 Testkriterien vor:

  1. Stelle sicher, dass Skiplinks die ersten focussierbaren Elemente der Webseite darstellen.
  2. Stelle sicher, dass die Linkbeschreibung eine verständliche Beschreibung des Sprungzieles enthält.
  3. Stelle sicher, dass der Skiplink entweder immer oder zumindest dann sichtbar ist, wenn der Tastaturfocus auf ihm liegt.
  4. Stelle sicher, dass beim Aktivieren eines Skiplinks der visuelle Focus im Viewport des Browsers auf das Ziel gesetzt wird.
  5. Stelle sicher, dass nach dem Aktivieren des Skiplinks der Tastaturfocus auf das Zielelement gesetzt wurde.

Die WCAG ist unnachgiebig, was diese Kritierien betrifft: Alle 5 Testkriterien müssen erfüllt werden.

Die Grundlagen: Markup und Styling

Skiplinks sollten grundsätzlich vor dem eigentlichen Inhalt der Webseite eingebaut werden, um dem Testkriterium 1 (erstes focussierbares Element) gerecht zu werden. Eine einfache Kontrollmöglichkeit für diese Regel besteht darin, sich die Webseite mit deaktivierten Stylesheets anzuschauen. Die Skiplinks sollten dabei an erster Stelle stehen.

Die technische Umsetzung per HTML & CSS ist nicht weiter kompliziert. Das HTML-Grundgerüst bildet ein einfacher Hyperlink, als Ziel kann jedes beliebige HTML-Element der Webseite dienen, soweit es über eine ID eindeutig identifizierbar ist. In der Regel wird man hier auf z. B. den DIV-Container verweisen, welcher die Hauptnavigation oder die Hauptinhaltsbereich der Webseite beinhaltet.

Skiplinks sind nichts Neues mehr, deshalb will ich den technischen Teil an dieser Stelle kurz halten. Tomas Caspers hat vor einiger Zeit auf Einfach für Alle ein sehr eingängiges Tutorial verfasst, welches ich jedem Einsteiger in die Materie ans Herz legen möchte. Auch die Diskussion in den Kommentaren ist bei diesem Beitrag lesenswert. Der wichtigste Punkt, Thomas spricht es an, betrifft die gewählte Technik für das zwischenzeitliche Ausblenden der Skiplinks. Hier darf keinesfalls die CSS-Eigenschaft display:none verwendet werden, denn hierdurch ist der Link für Screenreader nicht mehr erreichbar – und somit nutzlos. Das Aus- und Einblenden von Skiplinks geschieht daher in der Regel durch eine absolute Positionierung, womit die auszublendenden Elemente einfach seitlich aus dem Viewport verschoben (versteckt) werden und bei Focussierung per Tastatur wieder in den sichtbaren Teil zurückgeholt werden.

Mehr Gestaltungsfreiheit

Wie anfangs beschrieben, sollten Skiplinks generell vor den eigentlichen Seiteninhalten im Quelltext stehen. Das Verstecken der Skiplinks ist nie ein Problem – wohl aber die Eingliederung ins Layout, sobald die Skiplinks sichtbar werden. Einerseits muss im Layout Platz (Leerraum) für die Skiplinks eingeplant werden, zum anderen müssen Sie für den Nutzer bei Aktivierung auch gut erkennbar (Kontrast, Schriftgröße) sein. In grafiklastigen Layouts kann dies recht aufwändig werden.

Aus diesem Grund liefere ich seit einiger Zeit (v3.2) bei meinem CSS-Framework YAML eine etwas aufwändigere Darstellungsvariante mit, die in allen Layout-Beispielen implementiert ist. Hierbei werden die Skiplinks nicht in das Layout integriert, sondern bei Focussierung in Form eines farbigen Balkens über dem Layout eingeblendet. Auf diese Weise eröffnen sich viel größere Freiheiten bei der Darstellung – ohne dass das eigentlich Layout in irgendeiner Form beeinflusst wird.

Soweit, so gut. Im Prinzip wäre ich hiermit am Ende des Beitrags, wenn es da nicht neuerdings ein ausgesprochen hässliches Problem mit einigen modernen Webbrowsern gäbe.

Folge dem weißen Kaninchen Tastaturfocus

In den aktuellen Versionen der Webkit-basierten Browser (Google Chrome, Apple Safari) sowie im aktuellen Internet Explorer 8 unter Windows 7 (unter Vista tritt das Problem im IE8 offenbar nicht auf) wird bei Aktivierung eines Skiplinks zwar der visuelle Focus auf das Zielelement ausgerichtet (WCAG 2.0, Testkriterium 4 erfüllt), der Tastaturfocus verbleibt jedoch auch nach Aktivierung auf dem Skiplink, er wird nicht verschoben. Damit ist das Testkriterium 5 nicht erfüllt, denn beim nächsten Druck auf die Tabulatortaste springt der Focus (viuseller Focus und Tastaturfocus) auf das focussierbare Element, welches dem zuvor aktivierten Skiplink im Quelltext folgt. Damit wird der Sinn der Skiplinks vollständig ausgehebelt, denn man befindet sich praktisch wieder bei (oder direkt hinter) den Skiplinks.

Wen trifft das Problem eigentlich?

Ich habe gestern Marco Zehe um Hilfe gebeten, einige Browser/Screenreader-Kombinationen durchzutesten. Marco arbeitet in der Regel mit NVDA und hat im Internet Explorer 8 unter Windows 7 keinerlei Probleme, NVDA greift hier offenbar korrigierend ein. Auch im Safari/Mac trat das Problem in Verbindung mit VoiceOver nicht auf – Google Chrome und Safari/Win sind hingegen nach Marcos Meinung "gar nicht accessible!". Eine Nutzung scheidet für Ihn daher generell aus – das Focusproblem ist damit nicht seines.

Anders sieht die Situation bei JAWS aus: Hier konnte Marco das Problem in Verbindung mit dem Internet Explorer 8 verifizieren. Freundlicherweise ist JAWS der mit Abstand am meisten genutzte Screenreader und auch der IE hat eine gewisse Verbreitung – das Focusproblem ist also überaus real und bedarf einer Lösung.

Workaround – Runde 1

Ich wurde seinerzeit durch einen Blogbeitrag von Paul Redcliffe auf einen cleveren JavaScript-Workaround aufmerksam (Demo). Der Workaround besteht darin, dass er den Skiplinks dynamisch einen Click-Event zuweist, um bei Aktivierung den Tastaturfocus per JavaScript zu setzen. Hier der zugehörige JavaScript-Code ...

var is_webkit = navigator.userAgent.toLowerCase().indexOf('webkit') > -1;
var is_opera = navigator.userAgent.toLowerCase().indexOf('opera') > -1;
if(is_webkit || is_opera) {
  var target = document.getElementById('skiptarget');
  target.href="#skiptarget";
  target.innerText="Start of main content";
  target.setAttribute("tabindex" , "0");
  document.getElementById('skiplink').setAttribute("onclick" , "document.getElementById('skiptarget').focus();");
}

Grundsätzlich funktioniert der Workaround, allerdings gefallen mir einige Details nicht, weshalb ich für YAML einen eigenen Fix geschrieben habe.

  • Die Variablen werden im globalen Scope definiert und kein Namespacing verwendet. Das mag im konkreten Fall nicht weiter stören, dennoch empfinde ich es als unsauber.
  • Das Script ist monoton auf einen einzigen Skiplink mit der ID skiplink ausgelegt (getElementById) und nicht flexibel erweiterbar. Das ist unzureichend, da zwei oder drei Skiplinks (z.B.: Navigation, Inhalt, Kontaktformular ...) keine Seltenheit sind.
  • Den Zielelementen wird innerhalb des Skripts das Attribut tabindex="0" zugewiesen. Das ist aus meiner Sicht keine gute Lösung, denn so wird das Zielelement permanent in die normale Tabreihenfolge aufgenommen, obwohl es bei Focussierung per Tastatur keinerlei Funktion bietet – im Gegensatz zu focussierbaren Links, dessen URL der Nutzer bei Aktivierung folgt.
  • Eine weiter Unschönheit ergibt sich durch tabindex="0" in Verbindung mit Apples Safari, denn dieser hebt das focussierte Element generell durch eine leuchtend blaue Umrandung per outline hervor. So angenehm diese Darstellung bei focussierten Links oder Formularelementen ist, so störend oder verwirrend kann die plötzliche Hervorhebung eines einfachen DIV-Containers im Layout sein.

Workaround – Runde 2

Letztlich konnte ich das Phänomen im aktuellen Opera nicht nachvollziehen und konzentriere mich daher nur auf Webkit-Browser und den Internet Explorer. Der nachfolgende Code ist (bis auf minimale Abweichungen) Bestandteil der aktuellen Version 3.2.1 von YAML und ist unter yaml/core/js/yaml-focusfix.js zu finden.

/**
 * "Yet Another Multicolumn Layout" - (X)HTML/CSS Framework
 *
 * (en) Workaround for Webkit browsers to fix focus problems when using skiplinks
 * (de) Workaround für Webkit browsers, um den Focus zu korrigieren, bei Verwendung von Skiplinks
 *
 * @copyright       Copyright 2005-2010, Dirk Jesse
 * @package         yaml
 */

var YAML_focusFix = {
  init: function() {
    var skipClass = 'skip';
 
    var userAgent = navigator.userAgent.toLowerCase();
    var is_webkit = userAgent.indexOf('webkit') > -1;
    var is_ie = userAgent.indexOf('msie') > -1;
    var i = 0;
    var links, skiplinks = [];
   
    if (is_webkit || is_ie) {
      // find skiplinks in modern browsers ...
      if ( document.getElementsByClassName !== undefined) {
        skiplinks = document.getElementsByClassName(skipClass);
   
        for (i=0; i<skiplinks.length; i++) {
          this.setTabIndex(skiplinks[ i ]);
        }
      } else {
        // find skiplinks in older browsers ...
        links = document.getElementsByTagName('a');
        for (i=0; i<links.length; i++) {
          var s = links[ i ].getAttribute('href');
          var c = links[ i ].getAttribute('class');
          if (s.length > 1 && c.indexOf(skipClass) != -1 && s.substr(0, 1) == '#' ) {
            this.setTabIndex(links[ i ]); 
          }
        }
      } 
    }
  },

  setTabIndex: function( skiplink ){
    var target = skiplink.href.substr(skiplink.href.indexOf('#')+1);
    var targetElement = document.getElementById(target);
   
    if (targetElement !== null) {
      // make element accessible for .focus() method 
      targetElement.setAttribute("tabindex", "-1");
      skiplink.setAttribute("onclick", "document.getElementById('"+target+"').focus();"); 
    }
  }
};

YAML_focusFix.init();

Welche Änderungen habe ich nun im Detail vorgenommen? Das Script springt nun sowohl bei der Webkit-Engine, als auch beim Internet Explorer an und kann mehrere Skiplinks verarbeiten, indem es alle Links mit der Klasse .skip auswertet und entsprechend mit dem Workaround versieht. In YAML-basierte Layouts kann man es daher ohne weitere Konfiguration einbinden.

Beim Internet Explorer wurde bewusst auf einen Versionstest verzichtet, denn wie in dem weiter oben verlinkten Tutorial von Tomas Caspers zu lesen, haben die älteren IE-Versionen ein ähnliches Problem, sobald das Zielelement das proprietäre Merkmal hasLayout nicht besitzt. Da ich per JavaScript schlecht ins Layout eingreifen kann, um hasLayout zu triggern, ist es einfacher, das Script auch in den älteren IE’s pauschal durchlaufen zu lassen. Und letzten Endes setzt das Script auf dem Zielelement das Attribut tabindex="-1". Der Wert -1 erlaubt das dynamische Setzen des Tastaturfocus per JavaScript, fügt das Element aber nicht in die Tabreihenfolge ein. Damit werden die oben beschriebenen, unschönen Nebeneffekte von tabindex="0" vermieden.

Fazit

Ich muss gestehen, ich bin von der aktuellen Situation alles andere als begeistert. Nicht einmal ein so einfaches Konzept wie Skiplinks lässt sich problemfrei crossbrowser implementieren – auch nicht in den sonst so hoch gelobten, 100% ACID 3 unterstützenden Vertretern Chrome oder Safari. Erschwerend hinzu kommt, dass man – zumindest soviel ich im Moment weiß – um JavaScript nicht herumkommt.

Nun kann ich mir einerseits auf die Schulter klopfen, wie schlau ich doch bin, weil ich das Problem in den Griff bekommen habe. Doch das kann nicht darüber hinwegtäuschen, dass die Implementation von Skiplinks im normalen Webdesign-Alltag eigentlich nicht mehr als eine Fingerübung sein sollte und kein für Einsteiger fast unüberwindlicher Stolperstein (wer bringt als Einsteiger schon JS- und DOM-Kenntnisse mit).


Du kannst die Kommentare zu diesen Eintrag durch den RSS 2.0 Feed verfolgen. Du kannst einen Kommentar schreiben, oder einen Trackback auf deiner Seite einrichten.

Dieser Eintrag kann nicht mehr kommentiert werden.