Случайно опубликовано! Пожалуйста, зайдите позже, чтобы узнать больше!
Создание доступных веб-приложений — это не просто хорошая практика, теперь это необходимость. Недавно у меня появилась возможность создать навигационное меню с акцентом на a11y. В ходе исследования я понял, что большинство строк меню не соответствуют шаблону ARIA. Например, знаете ли вы, что вместо перемещения по пунктам меню с помощью табуляции по строке меню следует перемещаться с помощью клавиш со стрелками и управлять собственным фокусом?
Хотя я нашел несколько руководств, я не полностью им следовал. Я пишу это, потому что считаю, что то, что я в итоге создал, стоит того, чтобы поделиться им — если вам также нравятся небольшие компоненты и специальные хуки.
Хотя я структурирую этот блог с помощью некоторых этапов разработки, моя цель не состоит в том, чтобы написать пошаговое руководство. Я надеюсь, что вы знаете основы React и то, как работают пользовательские перехватчики.
Сейчас я делюсь только ключевыми деталями реализации, но планирую дополнить эту статью примером песочницы кода в будущем, когда у меня будет больше времени.
В этом блоге мы создаем навигационное меню, подобное тем, которые вы видите вверху или сбоку многих веб-приложений. В этой строке меню некоторые пункты меню могут иметь подменю, которые открываются/закрываются при вводе/выключении мыши.
Прежде всего, для доступности необходимы семантический HTML и соответствующие роли и атрибуты ARIA. Подробнее о шаблоне меню можно прочитать в официальном документе здесь.
Вот пример соответствующей HTML-разметки:
Обратите внимание, что мы используем тег кнопки для семантического HTML. Кнопка также должна иметь 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(), чтобы сфокусироваться на элементе при каждом изменении фокуса:
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 (
Обратите внимание, что это тег, который должен иметь ссылку (кнопка для пункта меню с подменю), поэтому, когда они находятся в фокусе, поведение клавиатуры по умолчанию сработает, как и ожидалось, например, навигация по Enter. Более того, индекс табуляции назначается правильно в зависимости от элемента, на котором находится фокус.
Мы добавляем обработчик событий для события фокуса, если событие фокуса не связано с событием клавиши/мыши. Вот цитата из веб-документа:
Не думайте, что все изменения фокуса будут происходить с помощью событий клавиатуры и мыши: вспомогательные технологии, такие как программы чтения с экрана, могут установить фокус на любой фокусируемый элемент.
Если вы последуете описанному выше useEffect, вы обнаружите, что первый элемент будет иметь фокус, даже если пользователь не использовал клавиатуру для навигации. Чтобы исправить это, мы можем проверить активный элемент и вызывать focus() только тогда, когда пользователь запустил какое-либо событие клавиатуры, которое смещает фокус с тела.
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