Mit der richtungsbasierten Fokusnavigation (über Pfeiltasten oder Steuerkreuze) können Nutzer, die nur die Tastatur oder alternative Eingabemethoden verwenden, interaktive Elemente anhand ihres visuellen 2D-Layouts aufrufen. Sie ist besonders nützlich für räumliche Benutzeroberflächen (z. B. Medienleisten, Dashboard-Bereiche oder verschachtelte Kataloge), muss aber sorgfältig implementiert werden, um native Browserfunktionen wie das Scrollen im Darstellungsbereich nicht zu beeinträchtigen.
In diesem Leitfaden wird beschrieben, wie Sie eine leistungsstarke, barrierefreie 2D-Richtungsnavigation in zusammengesetzten Widgets erstellen.
Grundlegende Architekturprinzipien
Beachte bei der Implementierung des benutzerdefinierten räumlichen Fokus mit den Pfeiltasten die folgenden grundlegenden Regeln:
- Auf zusammengesetzte Widgets beschränken: Überschreiben Sie die Pfeiltasten nicht im normalen Textfluss von Dokumenten oder in einfachen vertikalen Listenlayouts, in denen die standardmäßige sequenzielle Tab-Navigation erwartet wird. Allgemeine Hinweise zum Dokumentenfluss finden Sie in den Richtlinien für die Programmierung von Bedienungshilfen.
- Natives Scrollen am Rand beibehalten: Wenn es in der gedrückten Richtung kein fokussierbares Element gibt, wird das Standardverhalten des Browsers nicht verhindert. Lassen Sie das Ereignis „bubbeln“, damit der Browser den Darstellungsbereich nativ scrollen kann. Das Überschreiben von Pfeiltasten beeinträchtigt die Barrierefreiheit (WCAG 1.4.10).
- Sichtbarkeit und Ausrichtung des Fokus sicherstellen: Wenn sich der Fokus ändert, muss das neu fokussierte Element in den sichtbaren Bereich gescrollt werden (z.B. mit
scrollIntoView({ block: 'nearest', inline: 'nearest' })), damit es vollständig sichtbar bleibt. - Mit dem Layoutsystem koordinieren: Bestimmen Sie die Positionen der Fokuskandidaten anhand der Live-Begrenzungsgeometrien (
getBoundingClientRect()), während Sie jeden Tastendruck verarbeiten. Gehen Sie nicht davon aus, dass die DOM-Reihenfolge oder eine feste Rasterstruktur das visuelle Layout darstellt. Empfehlungen für moderne Layouts finden Sie unter CSS-Layouts und responsives Design.
Barrierefreie Fokusverwaltung
Der räumliche Container muss einen einzelnen Tabstopp für die sequenzielle Navigation auf der Seite bereitstellen. Verwalten Sie den Tastaturfokus im Widget mit dem Roving Tabindex Pattern:
- Das aktive Element hat
tabindex="0". - Alle inaktiven Elemente haben
tabindex="-1". - Verschieben Sie beim Navigieren den Fokus programmatisch, indem Sie das Attribut
tabindex="0"verschieben undfocus()aufrufen.
Visuelle Kandidatenauswahl
Bei asymmetrischen, umbrochenen oder Rasterlayouts schlägt die indexbasierte Zuordnung fehl. Leite stattdessen Kandidaten aus der visuellen Live-Geometrie für den aktuellen Tastendruck ab:
- Kandidaten: Verwenden Sie die fokussierbaren untergeordneten Elemente des Widget-Containers, die
checkVisibility()übergeben. Ausgeblendete Elemente können ein Rechteck der Größe 0 bei(0, 0)haben und dürfen nicht versehentlich zuup- oderleft-Zielen werden. - Geometrie:
getBoundingClientRect()für das fokussierte Element und jeden Kandidaten lesen, während der Tastendruck verarbeitet wird. Diese Rechtecke sollten nicht global im Cache gespeichert werden: Das Layout kann sich aus vielen Gründen ändern und eine kleine Anzahl von Geometrie-Lesevorgängen ist bei der Verarbeitung eines vom Nutzer ausgelösten Schlüsselereignisses kostengünstig. - Richtungsfilter: Ein Kandidat befindet sich nur dann in der gedrückten Richtung, wenn sich seine nahe Kante auf dieser Achse an oder jenseits der fernen Kante des aktuellen Elements befindet. Dabei ist eine kleine Toleranz für die Subpixel-Rundung zulässig. Ein Kandidat für
rightmuss beispielsweisecandidate.left >= current.right - tolerancehaben. Vergleichen Sie nicht die Mittelpunkte, um festzustellen, ob sich ein Kandidat in der Richtung befindet. - Ausgerichtete Kandidaten zuerst: Unter den Kandidaten, die den Richtungsfilter bestehen, werden Kandidaten bevorzugt, deren Ausdehnung auf der senkrechten Achse sich mit der Ausdehnung des aktuellen Elements überschneidet. Wählen Sie die kleinste Lücke auf der Navigationsachse aus und lösen Sie Gleichstände mit dem kleinsten senkrechten Mittelversatz auf. So bleibt die Navigation in der angrenzenden visuellen Zeile oder Spalte, auch wenn ein Element ungewöhnlich breit ist.
- Fallback-Bewertung: Wenn sich kein Kandidat auf der senkrechten Achse überschneidet, bewerten Sie die verbleibenden Kandidaten mit
gap + w * perpendicularCentreOffsetund wählen Sie den Mindestwert aus. Verwenden Sie als Ausgangspunkt eine Gewichtung vonw >= 2und passen Sie sie dann für das Layout an. Eine größere Gewichtung bevorzugt Kandidaten, die besser ausgerichtet sind. - Kein Kandidat: Es wird nichts zurückgegeben. Der Event-Handler darf
preventDefault()nicht aufrufen, damit der Browser nativ scrollen kann.
Die genaue Implementierung kann variieren, aber der Richtungstest sollte kantenbasiert sein und die Überlappungspräferenz sollte vor dem gewichteten Fallback angewendet werden. Das ist der Kern des Algorithmus. Vermeiden Sie es, einen Cache oder ein festes Gewicht als universell korrekt darzustellen.
Tastendruck-Integration und Scroll-Kooperation
Der Ereignisrouter darf das Standard-Viewport-Scrollen nur verhindern, wenn ein gültiger räumlicher Fokusübergang stattfindet:
- Das Ereignis darf nicht abgefangen werden, wenn eine Modifikatortaste gedrückt wird, wenn
e.defaultPreventedbereits „true“ ist oder wenn das innerste Ereignisziel selbst Pfeiltasten verwendet. Verwenden Siee.composedPath()[0]anstelle von nure.target, dae.targetauf einen Shadow-Host umgeleitet werden kann. Eine praktische Prüfung, ob ein Element editierbar ist oder ein Steuerelement ist, isttarget.matches(':read-write, select, input[type="range"], [role="slider"]'). Geschlossene Shadow-Roots können nicht geprüft werden. - Beschränken Sie den Listener auf den Widget-Container, damit mehrere räumliche Widgets auf einer Seite nebeneinander existieren können.
- Nur reagieren, wenn das fokussierte Element selbst ein Kandidat ist. Wenn Elemente eigene fokussierbare Steuerelemente enthalten, machen Sie diese Steuerelemente entweder ebenfalls zu Kandidaten oder lassen Sie sie in Ruhe. Lösen Sie ein übergeordnetes Element nicht mit
closest()auf. - Wenn ein Kandidat gefunden wird, rufen Sie
preventDefault()auf, aktualisieren Sie den Roving-tabindex, rufen Siefocus()auf und verwenden SiescrollIntoView({ block: 'nearest', inline: 'nearest' }). - Wenn kein Kandidat gefunden wird, wird die Funktion beendet, ohne
preventDefault()aufzurufen, sodass das native Scrollen fortgesetzt wird.
Vermeiden Sie es, für die räumliche Navigation Tastenkombinationen mit nur einem Zeichen wie h/j/k/l oder W/A/S/D hinzuzufügen. Pfeiltasten sind keine Zeichentasten. Wenn das native Scrollen beibehalten wird, wenn kein Kandidat vorhanden ist, wird die Bewegung des Darstellungsbereichs nicht unnötig blockiert.