Фокус при открытии/закрытии списка

Библиотека ally.js предназначена для упрощения работы с доступностью (accessibility) в веб-приложениях. Одним из ключевых аспектов доступного интерфейса является корректное управление фокусом при взаимодействии с динамическими элементами, такими как раскрывающиеся списки, модальные окна или всплывающие панели.

Фокус в браузере определяет, какой элемент активен для взаимодействия с клавиатурой. Некорректное управление фокусом приводит к проблемам с навигацией для пользователей, использующих клавиатуру или вспомогательные технологии.


Инициализация и базовая настройка

Для работы с фокусом ally.js предоставляет API tabbable и функции для установки фокуса на элементы. Пример инициализации:

import * as ally from 'ally.js';

const menuButton = document.querySelector('#menu-button');
const menuList = document.querySelector('#menu-list');

Здесь menuButton отвечает за открытие списка, а menuList — за сам список.


Управление фокусом при открытии списка

При открытии списка важно переместить фокус на первый интерактивный элемент внутри него, чтобы клавиатурная навигация оставалась логичной.

menuButton.addEventListener('click', () => {
    menuList.style.display = 'block';

    const firstFocusable = ally.query.firstTabbable(menuList);
    if (firstFocusable) {
        firstFocusable.focus();
    }
});

Ключевые моменты:

  • ally.query.firstTabbable(container) возвращает первый элемент внутри контейнера, доступный для табуляции.
  • Вставка фокуса с помощью focus() гарантирует, что пользователь сразу может взаимодействовать с элементами списка.

Для более сложных списков с динамическим контентом можно использовать tabbable:

const focusableItems = ally.query.tabbable(menuList);
focusableItems[0].focus();

Управление фокусом при закрытии списка

Закрытие списка должно возвращать фокус на элемент, который инициировал открытие. Это предотвращает потерю фокуса и улучшает навигацию.

function closeMenu() {
    menuList.style.display = 'none';
    menuButton.focus();
}

Если список закрывается автоматически (например, по клику вне элемента), важно отлавливать событие и корректно восстанавливать фокус:

document.addEventListener('click', (event) => {
    if (!menuList.contains(event.target) && event.target !== menuButton) {
        closeMenu();
    }
});

Защита фокуса внутри открытого списка

Для более сложных сценариев, таких как модальные меню или панели, полезно ограничивать фокус внутри контейнера:

import { trapFocus } from 'ally.js';

const trap = trapFocus(menuList, {
    context: document.body,
    ignore: [menuButton]
});

Особенности работы trapFocus:

  • Перехватывает нажатие клавиши Tab и циклически перемещает фокус внутри контейнера.
  • Можно исключать элементы, например кнопки управления, чтобы фокус оставался интуитивным.

Для отключения ограничения при закрытии списка используется метод disengage():

trap.disengage();

Динамическая проверка доступных элементов

Иногда список создается динамически, и необходимо проверять наличие интерактивных элементов перед установкой фокуса:

const focusableItems = ally.query.tabbable(menuList);

if (focusableItems.length > 0) {
    focusableItems[0].focus();
} else {
    menuButton.focus();
}

Это предотвращает ошибки, если список пуст или содержит элементы, недоступные для табуляции.


Поддержка клавиш управления

Для удобной навигации внутри списка рекомендуется обрабатывать клавиши Escape и Arrow:

menuList.addEventListener('keydown', (event) => {
    if (event.key === 'Escape') {
        closeMenu();
    }
});

Использование ally.js в сочетании с управлением фокусом позволяет создавать доступные интерфейсы без необходимости вручную проверять все элементы на доступность.


Оптимизация производительности

  • Кэширование элементов: если список часто открывается и закрывается, имеет смысл кешировать результаты ally.query.tabbable.
  • Минимизация пересчета: избегать повторных запросов DOM при каждом открытии, особенно для больших списков.
  • Обработчики событий: использовать делегирование для элементов внутри списка, чтобы не создавать отдельный обработчик на каждый элемент.

Рекомендации по совместимости

  • ally.js корректно работает с современными браузерами и поддерживает стандартные элементы управления (кнопки, ссылки, инпуты).
  • Для кастомных компонентов, таких как дивы с ролями menuitem, необходимо правильно задавать атрибут tabindex и роли ARIA, чтобы функции ally.js корректно определяли доступные элементы.