Библиотека ally.js предоставляет мощный набор инструментов для управления доступностью веб-интерфейсов, включая управление фокусом, обработку событий клавиатуры и взаимодействие с интерактивными элементами. Одной из часто встречающихся задач является закрытие модальных окон, выпадающих меню и других интерактивных панелей при нажатии клавиши ESC или при потере фокуса (blur).
Для обработки клавиши ESC в ally.js используется утилита
tabEvent или стандартные слушатели событий клавиатуры,
совмещённые с проверкой фокуса. Ключевой момент — корректно определить,
что фокус находится внутри целевого элемента, прежде чем инициировать
закрытие.
Пример базового подхода:
import ally from 'ally.js';
const modal = document.querySelector('#modal');
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
if (ally.hasFocus(modal)) {
closeModal();
}
}
});
function closeModal() {
modal.style.display = 'none';
modal.setAttribute('aria-hidden', 'true');
}
Особенности реализации:
ally.hasFocus(element) проверяет, находится ли
текущий фокус внутри указанного элемента.event.key === 'Escape' предпочтительнее,
чем проверка кодов клавиш, для лучшей поддержки разных устройств и
браузеров.В ряде случаев необходимо закрывать элементы, когда пользователь
кликает вне элемента или фокус покидает его. ally.js предоставляет
утилиты для отслеживания фокуса, включая методы focusWithin
и события focusout.
Пример реализации:
const dropdown = document.querySelector('#dropdown');
dropdown.addEventListener('focusout', (event) => {
// Проверка, что фокус ушёл полностью из элемента и его потомков
if (!dropdown.contains(event.relatedTarget)) {
closeDropdown();
}
});
function closeDropdown() {
dropdown.style.display = 'none';
dropdown.setAttribute('aria-hidden', 'true');
}
Ключевые моменты:
focusout срабатывает на элементе, когда фокус
покидает его или любой дочерний элемент.event.relatedTarget указывает на элемент, который
получает фокус после текущего. Проверка
!dropdown.contains(event.relatedTarget) гарантирует, что
закрытие произойдет только если фокус полностью покинул элемент.В сложных интерфейсах закрытие по ESC и blur часто нужно комбинировать, чтобы обеспечить интуитивное поведение:
Пример объединённого подхода:
function setupCloseBehavior(element, closeCallback) {
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && ally.hasFocus(element)) {
closeCallback();
}
});
element.addEventListener('focusout', (event) => {
if (!element.contains(event.relatedTarget)) {
closeCallback();
}
});
}
setupCloseBehavior(modal, closeModal);
setupCloseBehavior(dropdown, closeDropdown);
Преимущества такого подхода:
contains учитывает всех потомков.aria-hidden, aria-expanded), чтобы состояние
интерфейса было доступно для скринридеров.document.activeElement, чтобы корректно обрабатывать
элементы с вложенными интерактивными компонентами.focusout с mousedown или click на
документе для уверенного определения кликов вне элемента.Ally.js позволяет безопасно работать с динамически добавляемыми элементами:
const observer = new MutationObserver(() => {
const dynamicModals = document.querySelectorAll('.modal');
dynamicModals.forEach(modal => setupCloseBehavior(modal, () => modal.style.display = 'none'));
});
observer.observe(document.body, { childList: true, subtree: true });
Такой подход обеспечивает автоматическое подключение ESC и blur поведения к новым модальным окнам, появляющимся после AJAX-загрузок или других динамических изменений DOM.
Закрытие элементов по ESC и blur в ally.js требует внимательной работы с фокусом, событиями и атрибутами ARIA, но обеспечивает высокую доступность и предсказуемое поведение интерфейсов.