Tworzenie komponentu menu nawigacyjnego

Podstawowy przegląd tworzenia responsywnej i dostępnej na wszystkich urządzeniach ścieżki nawigacyjnej, która ułatwia użytkownikom poruszanie się po witrynie.

W tym poście chcę podzielić się z Tobą sposobem tworzenia komponentów ścieżki nawigacyjnej. Wypróbuj wersję demonstracyjną.

Demonstracja

Jeśli wolisz film, oto wersja tego posta w YouTube:

Omówienie

Komponent ścieżki menu nawigacyjnego wskazuje, w którym miejscu w hierarchii witryny znajduje się użytkownik. Nazwa pochodzi od Jaś i Małgosia, którzy w ciemnym lesie zostawili za sobą okruchy chleba, dzięki czemu mogli odnaleźć drogę do domu.

Ścieżka w tym poście nie jest standardową ścieżką, ale przypomina ścieżkę. Oferuje ona dodatkowe funkcje, umieszczając strony siostrza bezpośrednio w menu nawigacyjnym (z użyciem <select>), co umożliwia dostęp na wielu poziomach.

Tłogowisko

W powyższym filmie z prezentacją komponentu kategorie zastępcze to gatunki gier wideo. Ścieżka ta jest tworzona po przejściu przez tę ścieżkę: home » rpg » indie » on sale, jak pokazano poniżej.

Ten element ścieżki nawigacyjnej powinien umożliwiać użytkownikom poruszanie się po tej hierarchii informacji, czyli szybkie i bezbłędne przełączanie się między gałęziami i wybieranie stron.

Architektura informacji

Uważam, że warto myśleć w kategoriach kolekcji i elementów.

Kolekcje

Kolekcja to zbiór opcji do wyboru. Na stronie głównej prototypu tego posta znajdują się kolekcje gier FPS, RPG, brawler, dungeon crawler, sportowych i logicznych.

Elementy

Gra wideo jest elementem, a konkretna kolekcja może być elementem, jeśli reprezentuje inną kolekcję. Na przykład RPG to element i poprawna kolekcja. Jeśli jest to produkt, użytkownik znajduje się na stronie kolekcji. Na przykład na stronie z grami RPG, która zawiera listę gier RPG, w tym dodatkowe podkategorie AAA, Indie i Self Published.

W terminologii informatycznej ten element ścieżki stanowi wielowymiarową tablicę:

const rawBreadcrumbData = {
  "FPS": {...},
  "RPG": {
    "AAA": {...},
    "indie": {
      "new": {...},
      "on sale": {...},
      "under 5": {...},
    },
    "self published": {...},
  },
  "brawler": {...},
  "dungeon crawler": {...},
  "sports": {...},
  "puzzle": {...},
}

Twoja aplikacja lub witryna będzie mieć niestandardową architekturę informacji, która utworzy inną tablicę wielowymiarową, ale mam nadzieję, że koncepcja stron kolekcji i przechodzenia przez hierarchię może również uwzględniać Twoje ścieżki.

Układy

Znacznik

Dobre komponenty zaczynają się od odpowiedniego kodu HTML. W następnej sekcji omówię wybrane przeze mnie opcje znaczników i ich wpływ na cały komponent.

Schemat ciemny i jasny

<meta name="color-scheme" content="dark light">

Metatag color-scheme w powyższym fragmencie kodu informuje przeglądarkę, że ta strona chce używać jasnego i ciemnego stylu przeglądarki. Przykładowe elementy nawigacyjne nie zawierają kodu CSS dla tych schematów kolorów, dlatego będą używać domyślnych kolorów udostępnionych przez przeglądarkę.

<nav class="breadcrumbs" role="navigation"></nav>

Element <nav> należy używać do nawigacji po witrynie, która ma domyślną rolę ARIA nawigacji. Podczas testów zauważyliśmy, że atrybut role zmienia sposób interakcji czytnika ekranu z elementem. W fakcie był on ogłaszany jako element nawigacji, dlatego zdecydowaliśmy się go dodać.

Ikony

Gdy ikona jest powtarzana na stronie, element SVG <use> oznacza, że możesz zdefiniować path raz i używać go we wszystkich wystąpieniach ikony. Zapobiega to powtarzaniu się tych samych informacji o ścieżce, co powoduje większe dokumenty i potencjalną niespójność ścieżki.

Aby użyć tej techniki, dodaj do strony ukryty element SVG i owiń ikony w element <symbol> z unikalnym identyfikatorem:

<svg style="display: none;">

  <symbol id="icon-home">
    <title>A home icon</title>
    <path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
  </symbol>

  <symbol id="icon-dropdown-arrow">
    <title>A down arrow</title>
    <path d="M19 9l-7 7-7-7"/>
  </symbol>

</svg>

Przeglądarka odczytuje kod HTML SVG, zapisze informacje o ikonie w pamięci i przejdzie do dalszej części strony, która odwołuje się do identyfikatora ikony w celu jej dodatkowego użycia.

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-home" />
</svg>

<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
  <use href="#icon-dropdown-arrow" />
</svg>

Narzędzia dla programistów pokazujące wyrenderowany element SVG.

Zdefiniuj raz, używaj tyle razy, ile chcesz, z minimalnym wpływem na wydajność strony i elastycznym stylizowaniem. Do elementu SVG dodano aria-hidden="true". Ikony nie są przydatne dla osób, które przeglądają treści tylko przy użyciu dźwięku. Ukrywanie ich przed tymi użytkownikami zapobiega dodawaniu niepotrzebnego hałasu.

Właśnie w tym miejscu tradycyjne menu nawigacyjne różni się od tego w tym komponencie. Zwykle jest to tylko link <a>, ale dodałem interfejs użytkownika z ukrytym elementem select. Klasa .crumb odpowiada za rozmieszczenie linku i ikony, a klasa .crumbicon – za ułożenie ikony i elementu select obok siebie. Nazwałem go „linkiem dzielonym”, ponieważ jego funkcje są bardzo podobne do przycisku dzielonego, ale służy do nawigacji po stronach.

<span class="crumb">
  <a href="#sub-collection-b">Category B</a>
  <span class="crumbicon">
    <svg>...</svg>
    <select class="disguised-select" title="Navigate to another category">
      <option>Category A</option>
      <option selected>Category B</option>
      <option>Category C</option>
    </select>
  </span>
</span>

Link i kilka opcji to nic specjalnego, ale zwiększa funkcjonalność prostego menu nawigacyjnego. Dodanie elementu title do elementu <select> jest przydatne dla użytkowników czytników ekranu, ponieważ dostarcza im informacji o działaniu przycisku. Jednak ta sama pomoc jest dostępna dla wszystkich użytkowników, a na iPadzie jest widoczna na pierwszym planie. Jeden atrybut udostępnia kontekst przycisku wielu użytkownikom.

Zrzut ekranu z niewidocznym elementem, nad którym kursor i kontekstowy przycisk informacyjny

Dekoracje separatora

<span class="crumb-separator" aria-hidden="true">→</span>

Separatory są opcjonalne, wystarczy dodać tylko jeden (patrz trzeci przykład w filmie powyżej). Następnie dodaję aria-hidden="true", ponieważ są one dekoracyjne i nie muszą być odczytywane przez czytnik ekranu.

Właściwość gap, którą omówimy w następnym punkcie, ułatwia prawidłowe rozmieszczanie tych elementów.

Style

Ponieważ kolory są kolorami systemowymi, w większości są to luki i zbiory stylów.

Kierunek i przepływ układu

Narzędzia dla programistów pokazujące wyrównanie menu nawigacyjnego za pomocą funkcji nakładki flexbox.

Główny element nawigacji nav.breadcrumbs ustawia ograniczoną właściwość niestandardową do użycia przez elementy podrzędne, a także określa układ poziomy z wyrównaniem pionowym. Dzięki temu elementy ścieżki, separatory i ikony będą wyrównane.

.breadcrumbs {
  --nav-gap: 2ch;

  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  padding: calc(var(--nav-gap) / 2);
}

Element nawigacji pokazany w układaniu pionowym z nakładkami flexbox.

Każdy element .crumb tworzy też poziomy układ z niewielkim odstępem, ale kieruje się na elementy podrzędne linku i określa styl white-space: nowrap. Jest to bardzo ważne w przypadku menu nawigacyjnego z wielu słów, ponieważ nie chcemy, aby zajmowało ono kilka wierszy. W dalszej części tego posta dodamy style, które umożliwią obsłużenie poziomego przepełnienia przez usługę white-space.

.crumb {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--nav-gap) / 4);

  & > a {
    white-space: nowrap;

    &[aria-current="page"] {
      font-weight: bold;
    }
  }
}

Dodajemy aria-current="page", aby wyróżnić link do bieżącej strony na tle reszty. Użytkownicy czytników ekranu będą mieć wyraźny wskaźnik, że link prowadzi do bieżącej strony, ale także wizualnie wyróżniliśmy ten element, aby ułatwić korzystanie z niego użytkownikom ze wzrokiem.

Komponent .crumbicon używa siatki do ułożenia ikony SVG z „prawie niewidocznym” elementem <select>.

Narzędzie DevTools dla siatki nałożone na przycisk, w którym zarówno wiersz, jak i kolumna mają nazwę zdefiniowaną w zestawie.

.crumbicon {
  --crumbicon-size: 3ch;

  display: grid;
  grid: [stack] var(--crumbicon-size) / [stack] var(--crumbicon-size);
  place-items: center;

  & > * {
    grid-area: stack;
  }
}

Element <select> znajduje się na końcu modelu DOM, więc jest na szczycie stosu i jest interaktywny. Dodaj styl opacity: .01, aby element był nadal użyteczny. W efekcie pole wyboru będzie idealnie dopasowane do kształtu ikony. To dobry sposób na dostosowanie wyglądu elementu <select> przy zachowaniu wbudowanej funkcjonalności.

.disguised-select {
  inline-size: 100%;
  block-size: 100%;
  opacity: .01;
  font-size: min(100%, 16px); /* Defaults to 16px; fixes iOS zoom */
}

Rozwijany

Ścieżka powinna być w stanie reprezentować bardzo długi ślad. Lubię, gdy w odpowiednich przypadkach elementy wykraczają poza ekran w poziomie, i uważam, że ten komponent z linkami nawigacyjnymi jest w tym przypadku odpowiedni.

.breadcrumbs {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: calc(var(--nav-gap) / 2);

  & > .crumb:last-of-type {
    scroll-snap-align: end;
  }

  @supports (-webkit-hyphens:none) { & {
    scroll-snap-type: none;
  }}
}

Style przepełnienia umożliwiają takie działanie:

  • Przewijanie poziome z zatrzymaniem po przekroczeniu krawędzi.
  • Dopełnienie przesunięcia poziomego.
  • Jeden punkt dopasowania na ostatnim fragmencie ścieżki. Oznacza to, że po wczytaniu strony pierwszy elementCrumb wczytuje się w ramach widoku.
  • Usunięcie punktu zazębiania z Safari, który ma problemy z kombinacją przewijania poziomego i punktów zazębiania.

Zapytania multimedialne

Jednym z subtelnych dostosowań w przypadku mniejszych widoków jest ukrycie etykiety „Strona główna” i pozostawienie tylko ikony:

@media (width <= 480px) {
  .breadcrumbs .home-label {
    display: none;
  }
}

Porównanie elementów nawigacji z etykietą strony głównej i bez niej.

Ułatwienia dostępu

Ruch

W tym komponencie nie ma zbyt wielu ruchów, ale dzięki umieszczeniu przejścia w ramach kontroli prefers-reduced-motion możemy zapobiec niechcianym ruchom.

@media (prefers-reduced-motion: no-preference) {
  .crumbicon {
    transition: box-shadow .2s ease;
  }
}

Żaden z innych stylów nie wymaga zmiany, efekty najechania kursorem i wyświetlenia są świetne i znaczące bez transition, ale jeśli animacja jest w porządku, dodamy subtelną animację do interakcji.

JavaScript

Po pierwsze, niezależnie od typu routera używanego w witrynie lub aplikacji, gdy użytkownik zmieni informacje w śladach, adres URL musi zostać zaktualizowany, a użytkownik musi zobaczyć odpowiednią stronę. Po drugie, aby ujednolicić wrażenia użytkowników, zadbaj o to, aby nie było nieoczekiwanych przejść, gdy użytkownicy przeglądają <select>opcje.

2 kluczowe wskaźniki dotyczące wrażeń użytkowników, które obsługuje JavaScript: select has changed i eager <select> zapobieganie wywoływaniu zdarzeń zmiany.

Pośpieszna zapobieganie zdarzeniom jest konieczne ze względu na użycie elementu <select>. W przeglądarce Edge na Windowsie (i prawdopodobnie też w innych przeglądarkach) zdarzenie select changedwyzwala się, gdy użytkownik przegląda opcje za pomocą klawiatury. Dlatego nazywam je „niecierpliwe”, ponieważ użytkownik wybrał opcję tylko pozornie, np. przez najechanie kursorem lub skupienie się na niej, ale nie potwierdził wyboru za pomocą przycisku enter ani click. Zdarzenie o szybkim działaniu powoduje, że funkcja zmiany kategorii komponentu jest niedostępna, ponieważ otwarcie pola wyboru i zwykłe przeglądanie produktu powoduje wywołanie zdarzenia i zmianę strony, zanim użytkownik będzie gotowy.

Lepsze zdarzenie <select> zostało zmienione

const crumbs = document.querySelectorAll('.breadcrumbs select')
const allowedKeys = new Set(['Tab', 'Enter', ' '])
const preventedKeys = new Set(['ArrowUp', 'ArrowDown'])

// watch crumbs for changes,
// ensures it's a full value change, not a user exploring options via keyboard
crumbs.forEach(nav => {
  let ignoreChange = false

  nav.addEventListener('change', e => {
    if (ignoreChange) return
    // it's actually changed!
  })

  nav.addEventListener('keydown', ({ key }) => {
    if (preventedKeys.has(key))
      ignoreChange = true
    else if (allowedKeys.has(key))
      ignoreChange = false
  })
})

Strategia polega na sprawdzaniu zdarzeń naciśnięcia klawisza w przypadku każdego elementu <select> i określaniu, czy naciśnięty klawisz był potwierdzeniem nawigacji (Tab lub Enter) czy nawigacją przestrzenną (ArrowUp lub ArrowDown). Dzięki temu komponent może zdecydować, czy zaczekać, czy wykonać działanie, gdy zostanie wywołane zdarzenie elementu <select>.

Podsumowanie

Teraz, gdy już wiesz, jak to zrobić, jak Ty to zrobisz? 🙂

Zróżnicujemy nasze podejścia i poznamy wszystkie sposoby tworzenia stron internetowych. Utwórz wersję demonstracyjną, wyślij mi linki, a ja dodam je do sekcji z remiksami społeczności.

Remiksy społeczności