誤って公開してしまいました!詳細については、後でもう一度お読みください。
アクセシビリティ対応の Web アプリケーションを作成することは、単なる良い習慣ではなく、今や必須となっています。最近、a11y に焦点を当てたナビゲーション メニューバーを構築する機会がありました。調べているうちに、世の中のほとんどのメニューバーが ARIA パターンに準拠していないことに気づきました。たとえば、メニュー項目をタブで移動する代わりに、メニューバーは矢印キーで移動し、独自のフォーカスを管理する必要があることをご存知ですか?
いくつかのチュートリアルを見つけましたが、結局完全に従うことはできませんでした。私がこれを書いているのは、私が最終的に構築したものは共有する価値があると思うからです。もしあなたが小さなコンポーネントやカスタムフックにも興味があるなら。
このブログはいくつかの開発ステップで構成しますが、私の目標はステップバイステップのガイドを書くことではありません。あなたは React の基本とカスタム フックがどのように機能するかを理解していると思います。
現在は主要な実装の詳細のみを共有していますが、将来的には時間があるときにコード サンドボックスの例を含めてこの記事を更新する予定です。
このブログでは、多くの Web アプリケーションの上部または側面にあるようなナビゲーション メニュー バーを目指して構築しています。このメニュー バーでは、一部のメニュー項目にサブ メニューがあり、マウスの入力/終了で開閉します。
何よりもまず、セマンティック HTML と適切なロールと ARIA 属性がアクセシビリティに不可欠です。メニューバーのパターンについては、ここの公式ドキュメントから詳細を読むことができます。
適切な HTML マークアップの例を次に示します:
セマンティック HTML に button タグを使用していることに注意してください。ボタンには、スクリーン リーダーに警告する aria-haspopup も必要です。最後に、メニューの状態に応じて、適切な aria-expanded 属性を割り当てる必要があります。
必要なコンポーネントについて見ていきましょう。明らかに、メニュー項目コンポーネントだけでなく、メニュー全体のコンポーネントも必要です。
メニュー項目にはサブメニューがあるものとサブメニューがないものがあります。サブメニューのあるメニュー項目は、ホバーイベントやキーボードイベントでのサブメニューの開閉の状態を管理する必要があります。したがって、それは独自のコンポーネントである必要があります。
サブメニューも独自のコンポーネントである必要があります。サブメニューもメニュー項目の単なるコンテナですが、サブメニューの状態を管理したり、キーボード イベントを処理したりすることはありません。これにより、トップレベルのナビゲーション メニューと区別されます。
最終的に次のコンポーネントを作成しました:
非常に簡単に言うと、「フォーカス管理」とは、コンポーネントがどの子にフォーカスを持っているかを知る必要があることを意味します。したがって、ユーザーのフォーカスが離れて戻ってくると、以前にフォーカスされていた子が再度フォーカスされます。
フォーカス管理の一般的な手法は「ロービング タブ インデックス」です。グループ内のフォーカスされた要素のタブ インデックスは 0 になり、他の要素のタブ インデックスは -1 になります。こうすることで、ユーザーがフォーカス グループに戻ると、タブ インデックス 0 の要素が自動的にフォーカスを持ちます。
NavMenu の最初の実装は次のようになります:
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 ( ); }
e.preventDefault() は、ArrowDown によるページのスクロールなどを防ぐためにあります。
これが MenuItem コンポーネントです。サブメニューのある項目は少し無視してみましょう。 useEffect、usePrevious、element.focus() を使用して、focusedIndex が変更されるたびに要素にフォーカスします:
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 (
これは ref (サブメニューのあるメニュー項目のボタン) を持つ必要があるタグであることに注意してください。そのため、これらのタグにフォーカスすると、Enter キーでのナビゲーションなど、デフォルトのキーボード動作が期待どおりに開始されます。さらに、フォーカスされている要素に応じてタブインデックスが適切に割り当てられています。
フォーカス イベントがキー/マウス イベントによるものではない場合に備えて、フォーカス イベント用のイベント ハンドラーを追加しています。以下はウェブドキュメントからの引用です:
すべてのフォーカス変更がキー イベントやマウス イベントによってもたらされるとは考えないでください。スクリーン リーダーなどの支援技術は、フォーカス可能な要素にフォーカスを設定できます。
上記の useEffect に従うと、ユーザーがキーボードを使用して移動しなくても、最初の要素にフォーカスがあることがわかります。これを修正するには、アクティブな要素を確認し、ユーザーがキーボード イベントを開始したときにのみ focus() を呼び出します。これにより、フォーカスが body から移動します。
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]);
これまでに、機能的な NavMenu コンポーネントと MenuItemLink コンポーネントができました。サブメニューのあるメニュー項目に進みましょう。
急いで構築しているときに、このメニュー項目がロジックの大部分を共有することに気づきました
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3