Heim  >  Artikel  >  Web-Frontend  >  Erstellen einer barrierefreien Navigationsmenüleiste mit React Hooks

Erstellen einer barrierefreien Navigationsmenüleiste mit React Hooks

WBOY
WBOYOriginal
2024-08-08 13:25:29715Durchsuche

Building an Accessible Navigation Menubar with React Hooks

Aus Versehen veröffentlicht! Bitte kommen Sie später noch einmal vorbei, um mehr zu erfahren!

Einführung

Das Erstellen barrierefreier Webanwendungen ist nicht nur eine gute Praxis – es ist jetzt eine Notwendigkeit. Kürzlich hatte ich die Gelegenheit, eine Navigationsmenüleiste mit Schwerpunkt auf a11y zu erstellen. Bei meinen Recherchen wurde mir klar, dass die meisten Menüleisten nicht dem ARIA-Muster entsprechen. Wussten Sie beispielsweise, dass eine Menüleiste mit Pfeiltasten navigiert werden und ihren eigenen Fokus verwalten sollte, anstatt durch Menüelemente zu scrollen?

Obwohl ich einige Tutorials gefunden habe, habe ich sie letztendlich nicht vollständig befolgt. Ich schreibe dies, weil ich denke, dass das, was ich letztendlich gebaut habe, es wert ist, geteilt zu werden – wenn Sie auch eine Affinität zu kleinen Komponenten und benutzerdefinierten Haken haben.

Obwohl ich diesen Blog mit einigen Entwicklungsschritten strukturieren werde, ist es nicht mein Ziel, eine Schritt-für-Schritt-Anleitung zu schreiben. Ich vertraue darauf, dass Sie die Grundlagen von React kennen und wissen, wie benutzerdefinierte Hooks funktionieren.

Ich teile jetzt nur die wichtigsten Implementierungsdetails mit, aber ich habe vor, diesen Artikel in Zukunft mit einem Code-Sandbox-Beispiel zu aktualisieren, wenn ich mehr Zeit habe.

Was bauen wir?

Für diesen Blog entwickeln wir eine Navigationsmenüleiste, wie Sie sie oben oder an der Seite vieler Webanwendungen sehen. In dieser Menüleiste können einige Menüelemente Untermenüs haben, die beim Betreten/Verlassen mit der Maus geöffnet/geschlossen werden.

HTML-Markup

In erster Linie sind semantisches HTML und entsprechende Rollen und ARIA-Attribute für die Barrierefreiheit unerlässlich. Weitere Informationen zum Menüleistenmuster finden Sie im offiziellen Dokument hier.

Hier ist ein Beispiel für geeignetes HTML-Markup:

<nav aria-label="Accessible Menubar">
  <menu role="menubar">
    <li role="none">
      <a role="menuitem" href="/">Home</a>
    </li>
    <li role="none">
      <a role="menuitem" href="/about">About</a>
    </li>
    <li role="none">
      <button 
        role="menuitem" 
        aria-haspopup="true"
        aria-expanded="false"
      >
        Expand Me!
      </button>
      <menu role="menu">
        <li role="none">
          <a role="menuitem" href="/sub-item-1">Sub Menu Item 1</a>
        </li>
        <li role="none">
          <a role="menuitem" href="/sub-item-2">Sub Menu Item 2</a>
        </li>
      </menu>
    </li>
  </menu>
</nav>

Beachten Sie, dass wir das Button-Tag für semantisches HTML verwenden. Die Schaltfläche sollte auch über aria-haspopup verfügen, um Screenreader darauf aufmerksam zu machen. Abschließend sollte je nach Menüstatus das entsprechende aria-expanded-Attribut zugewiesen werden.

Komponenten

Lassen Sie uns die Komponenten durchgehen, die wir benötigen. Natürlich benötigen wir eine allgemeine Menükomponente sowie eine Menüelementkomponente.

Einige Menüpunkte haben ein Untermenü, andere nicht. Die Menüelemente mit Untermenüs müssen ihren Status für das Öffnen/Schließen von Untermenüs bei Hover- und Tastaturereignissen verwalten. Es muss also eine eigene Komponente sein.

Untermenüs müssen ebenfalls eine eigene Komponente sein. Obwohl Untermenüs auch nur Container für Menüelemente sind, verwalten sie weder deren Status noch verarbeiten sie Tastaturereignisse. Dies unterscheidet sie vom Navigationsmenü der obersten Ebene.

Am Ende habe ich diese Komponenten geschrieben:

  • NavMenu für die äußerste Ebene der Menüleisten.
  • MenuItem für einzelne Menüpunkte.
    • MenuItemLink
    • MenuItemWithSubMenu
  • Untermenü für das erweiterte Untermenü. MenuItem kann rekursiv innerhalb des Untermenüs verschachtelt werden.

Fokusmanagement

Ganz einfach ausgedrückt bedeutet „Fokusmanagement“ lediglich, dass die Komponente wissen muss, welches Kind den Fokus hat. Wenn also der Fokus des Benutzers verschwindet und wieder zurückkehrt, wird das zuvor fokussierte Kind wieder fokussiert.

Eine gängige Technik zur Fokusverwaltung ist der „Roving Tab Index“, bei dem das fokussierte Element in der Gruppe einen Tab-Index von 0 und andere Elemente einen Tab-Index von -1 haben. Wenn der Benutzer auf diese Weise zur Fokusgruppe zurückkehrt, hat das Element mit dem Tab-Index 0 automatisch den Fokus.

Eine erste Implementierung für NavMenu kann etwa so aussehen:

export function NavMenu ({ menuItems }) {
  // state for the currently focused index
  const [focusedIndex, setFocusedIndex] = useState(0);

  // functions to update focused index
  const goToStart = () => setCurrentIndex(0);
  const goToEnd = () => setCurrentIndex(menuItems.length - 1);
  const goToPrev = () => {
    const index = currentIndex === 0 ? menuItems.length - 1 : currentIndex - 1;
    setCurrentIndex(index);
  };
  const goToNext = () => {
    const index = currentIndex === menuItems.length - 1 ? 0 : currentIndex + 1;
    setCurrentIndex(index);
  };

  // key down handler according to aria specification
  const handleKeyDown = (e) => {
    e.stopPropagation();
    switch (e.code) {
      case "ArrowLeft":
      case "ArrowUp":
        e.preventDefault();
        goToPrev();
        break;
      case "ArrowRight":
      case "ArrowDown": 
        e.preventDefault();
        goToNext();
        break;
      case "End":
        e.preventDefault();
        goToEnd();
        break;
      case "Home":
        e.preventDefault();
        goToStart();
        break;
      default:
        break;
    }
  }

  return (
    <nav>
      <menu role="menubar" onKeyDown={handleKeyDown}>
        {menuItems.map((item, index) => 
          <MenuItem
            key={item.label}
            item={item}
            index={index}
            focusedIndex={focusedIndex}
            setFocusedIndex={setFocusedIndex}
          />
        )}
      </menu>
    </nav>
  );
}

Die Funktion e.preventDefault() dient dazu, Dinge wie das Scrollen mit ArrowDown auf der Seite zu verhindern.

Hier ist die MenuItem-Komponente. Lassen Sie uns die Elemente mit Untermenü für eine Sekunde ignorieren. Wir verwenden useEffect, usePrevious und element.focus(), um den Fokus auf das Element zu richten, wann immer sich der focusIndex ändert:

export function MenuItem ({ item, index, focusedIndex, setFocusedIndex }) {
  const linkRef = useRef(null);
  const prevFocusedIndex = usePrevious(focusedIndex);
  const isFocused = index === focusedIndex;

  useEffect(() => {
    if (linkRef.current 
      && prevFocusedIndex !== currentIndex 
      && isFocused) {
      linkRef.current.focus()
    }
  }, [isFocused, prevFocusedIndex, focusedIndex]);

  const handleFocus = () => {
    if (focusedIndex !== index) {
      setFocusedIndex(index);
    }
  };

  return (
    <li role="none">
      <a 
        ref={linkRef} 
        role="menuitem"
        tabIndex={isFocused ? 0 : -1}
        onFocus={handleFocus}
      >
        {item.label}
      </a>
    </li>
  );
}

Beachten Sie, dass es sich um das Tag „a“ handelt, das die Schaltfläche „ref“ (Schaltfläche für Menüelement mit Untermenüs) haben sollte. Wenn Sie also den Fokus darauf legen, werden die standardmäßigen Tastaturverhalten wie erwartet aktiviert, z. B. die Navigation bei der Eingabetaste. Darüber hinaus wird der Tab-Index je nach fokussiertem Element richtig zugewiesen.

Wir fügen einen Ereignishandler für Fokusereignisse hinzu, falls das Fokusereignis nicht von einem Tasten-/Mausereignis stammt. Hier ist ein Zitat aus dem Webdokument:

Gehen Sie nicht davon aus, dass alle Fokusänderungen über Tasten- und Mausereignisse erfolgen: Hilfstechnologien wie Bildschirmleseprogramme können den Fokus auf jedes fokussierbare Element setzen.

Optimierung Nr. 1

Wenn Sie dem oben beschriebenen useEffect folgen, werden Sie feststellen, dass das erste Element den Fokus hat, auch wenn der Benutzer nicht die Tastatur zum Navigieren verwendet hat. Um dies zu beheben, können wir das aktive Element überprüfen und focus() nur aufrufen, wenn der Benutzer ein Tastaturereignis gestartet hat, das den Fokus vom Körper weg verschiebt.

  useEffect(() => {
    if (linkRef.current 
      && document.activeElement !== document.body // only call focus when user uses keyboard navigation
      && prevFocusedIndex !== focusedIndex
      && isCurrent) {
      linkRef.current.focus();
    }
  }, [isCurrent, focusedIndex, prevFocusedIndex]);

Logic Reuse and Custom Hook

So far, we have functional NavMenu and MenuItemLink components. Let's move on to menu item with sub menus.

As I was quickly building it out, I realized that this menu item will share the majority of the logic

Das obige ist der detaillierte Inhalt vonErstellen einer barrierefreien Navigationsmenüleiste mit React Hooks. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn