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:
- 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.
- 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).
- 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. - 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ącfocus().
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 celamiupanileft. - 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
rightmusi 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 * perpendicularCentreOffseti wybierz minimum. Użyj wagiw >= 2jako 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.defaultPreventedma już wartość true lub gdy najbardziej wewnętrzny cel zdarzenia sam wykorzystuje klawisze strzałek. Używaje.composedPath()[0]zamiaste.target, ponieważe.targetmoże zostać przekierowany do hosta shadow. Praktyczny test edytowalności/kontroli totarget.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łajfocus()i użyjscrollIntoView({ 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.