Nawigacja przestrzenna (fokus klawiatury kierunkowej)

Funkcje internetowe: scrollIntoView()

Nawigacja kierunkowa (za pomocą klawiszy strzałek lub padów kierunkowych) umożliwia użytkownikom korzystającym wyłącznie z klawiatury i alternatywnych metod wprowadzania danych poruszanie się po elementach interaktywnych na podstawie ich wizualnego układu 2D. Jest to bardzo przydatne w przypadku interfejsów przestrzennych (takich jak paski multimediów, panele panelu lub zagnieżdżone katalogi), ale należy je wdrażać ostrożnie, aby nie zakłócać działania natywnych funkcji przeglądarki, takich jak przewijanie widocznego obszaru.

W tym przewodniku opisujemy, jak utworzyć wydajną i dostępną nawigację kierunkową 2D w widżetach złożonych.


Podstawowe zasady architektury

Podczas wdrażania niestandardowego zaznaczania przestrzennego za pomocą klawiszy strzałek przestrzegaj tych podstawowych zasad:

  1. Ogranicz do widżetów złożonych: nie zastępuj klawiszy strzałek w normalnym przepływie tekstu dokumentu ani w prostych układach list pionowych, w których oczekiwana jest domyślna sekwencyjna nawigacja za pomocą klawisza Tab. Ogólne wskazówki dotyczące przepływu dokumentu znajdziesz w wytycznych dotyczących kodowania ułatwień dostępu.
  2. Zachowaj natywne przewijanie do krawędzi: jeśli w kierunku naciśnięcia nie ma elementu, który można zaznaczyć, nie blokuj domyślnego działania przeglądarki. Zezwól na bąbelkowanie zdarzenia, aby przeglądarka mogła natywnie przewijać widoczny obszar. Zastępowanie klawiszy strzałek bezwarunkowo narusza ułatwienia dostępu (WCAG 1.4.10).
  3. Zapewnij widoczność i wyrównanie fokusu: gdy fokus się zmienia, upewnij się, że nowo zaznaczony element jest przewijany do widoku (np. za pomocą scrollIntoView({ block: 'nearest', inline: 'nearest' })), aby był w pełni widoczny.
  4. Koordynacja z systemem układu: określanie pozycji kandydatów do zaznaczenia na podstawie geometrii aktywnych ramek ograniczających (getBoundingClientRect()) podczas obsługi każdego naciśnięcia klawisza. Nie zakładaj, że kolejność DOM lub stała struktura siatki odzwierciedla układ wizualny. Więcej informacji o nowoczesnych układach znajdziesz w artykule Układy CSS i elastyczne projektowanie stron.

Zarządzanie skupieniem uwagi z ułatwieniami dostępu

Kontener przestrzenny musi udostępniać pojedynczy punkt tabulacji na potrzeby sekwencyjnej nawigacji po kartach strony. W widżecie zarządzaj zaznaczeniem klawiatury za pomocą wzorca Roving Tabindex:

  • Aktywny element ma tabindex="0".
  • Wszystkie nieaktywne elementy mają tabindex="-1".
  • Podczas nawigacji programowo przenoś zaznaczenie, zmieniając atrybut tabindex="0" i wywołując focus().

Wybór kandydata wizualnego

W przypadku układów asymetrycznych, zawijanych lub siatki mapowanie oparte na indeksie nie działa. Zamiast tego wygeneruj kandydatów na podstawie geometrii wizualnej na żywo dla bieżącego naciśnięcia klawisza:

  • Kandydaci: używaj elementów podrzędnych kontenera widżetu, które mogą być zaznaczone i które przechodzą test checkVisibility(). Ukryte elementy mogą mieć prostokąt o rozmiarze 0 w (0, 0) i nie mogą stać się przypadkowymi celami up ani left.
  • Geometria: odczytaj getBoundingClientRect() dla wybranego elementu i każdego kandydata podczas obsługi naciśnięcia klawisza. Nie przechowuj tych prostokątów globalnie w pamięci podręcznej: układ może się zmienić z wielu powodów, a niewielka liczba odczytów geometrii jest tania podczas obsługi kluczowego zdarzenia wywołanego przez użytkownika.
  • Filtr kierunku: kandydat znajduje się w kierunku naciśnięcia tylko wtedy, gdy jego najbliższa krawędź znajduje się na najdalszej krawędzi bieżącego elementu na tej osi lub poza nią, co pozwala na niewielką tolerancję zaokrąglania subpikseli. Na przykład kandydat na right musi mieć candidate.left >= current.right - tolerance. Nie porównuj środków, aby określić, czy kandydat znajduje się w kierunku.
  • Najpierw wyrównane elementy kandydujące: spośród elementów kandydujących, które przechodzą przez filtr kierunku, preferuj te, których zakres na osi prostopadłej pokrywa się z zakresem bieżącego elementu. Wybierz najmniejszą przerwę na osi nawigacji, rozstrzygając remisy za pomocą najmniejszego prostopadłego przesunięcia środka. Dzięki temu nawigacja pozostaje w sąsiednim wierszu lub sąsiedniej kolumnie, nawet jeśli element jest wyjątkowo szeroki.
  • Punktacja zastępcza: jeśli żaden kandydat nie pokrywa się na osi prostopadłej, oceń pozostałych kandydatów na gap + w * perpendicularCentreOffset i wybierz minimum. Użyj wagi w >= 2 jako punktu wyjścia, a następnie dostosuj ją do układu. Większa waga faworyzuje kandydatów, którzy są bardziej dopasowani.
  • Brak kandydata: nic nie zwracaj. Moduł obsługi zdarzeń nie może wywoływać metody preventDefault(), aby umożliwić przeglądarce natywne przewijanie.

Dokładna implementacja może się różnić, ale test kierunku powinien być oparty na krawędziach, a preferencje dotyczące nakładania się powinny być stosowane przed ważoną kreacją zastępczą. To jest sedno algorytmu. Unikaj przedstawiania pamięci podręcznej lub stałej wagi jako uniwersalnie poprawnej.


Integracja naciśnięć klawiszy i współpraca przy przewijaniu

Moduł obsługi zdarzeń musi zapobiegać standardowemu przewijaniu widocznego obszaru tylko wtedy, gdy następuje prawidłowe przejście fokusu przestrzennego:

  • Nie przechwytuj zdarzenia, gdy przytrzymany jest klawisz modyfikujący, gdy e.defaultPrevented ma już wartość true lub gdy najbardziej wewnętrzny cel zdarzenia sam wykorzystuje klawisze strzałek. Używaj e.composedPath()[0] zamiast e.target, ponieważ e.target może zostać przekierowany do hosta shadow. Praktyczny test edytowalności/kontroli to target.matches(':read-write, select, input[type="range"], [role="slider"]'). Nie można sprawdzać zamkniętych elementów shadow root.
  • Zakres detektora powinien obejmować kontener widżetu, aby na stronie mogło współistnieć wiele widżetów przestrzennych.
  • Działaj tylko wtedy, gdy zaznaczony element jest kandydatem. Jeśli elementy zawierają własne elementy sterujące, które można zaznaczyć, ustaw te elementy sterujące jako kandydatów lub pozostaw je bez zmian. Nie rozwiązuj elementu nadrzędnego za pomocą closest().
  • Po znalezieniu kandydata wywołaj preventDefault(), zaktualizuj zmienną tabindex, wywołaj focus() i użyj scrollIntoView({ block: 'nearest', inline: 'nearest' }).
  • Jeśli nie zostanie znaleziony żaden kandydat, zwróć wynik bez wywoływania metody preventDefault(), aby kontynuować przewijanie natywne.

Unikaj dodawania skrótów jednoliterowych, takich jak h/j/k/l lub W/A/S/D, do nawigacji przestrzennej. Klawisze strzałek nie są klawiszami znaków, a zachowanie natywnego przewijania, gdy nie ma kandydata, pozwala uniknąć niepotrzebnego blokowania ruchu w obszarze widoku.