Работа с большими списками интерактивных элементов в веб-интерфейсе
требует аккуратного управления фокусом. Стандартный HTML-подход с
tabindex быстро становится непрактичным при динамическом
контенте, где количество элементов может меняться. Библиотека
ally.js предоставляет набор инструментов для
эффективного контроля фокусируемых элементов, позволяя управлять их
доступностью и навигацией с клавиатуры.
Фокусируемые элементы — это те элементы DOM, на
которые можно перемещаться с помощью клавиши Tab или программно через
JavaScript. В HTML к ним относятся a[href],
button, input, select,
textarea, а также элементы с явным атрибутом
tabindex.
tabindex определяет порядок и
возможность фокусировки:
tabindex="0" — элемент включён в естественный порядок
табуляции.tabindex="-1" — элемент исключён из табуляции, но
доступен для программного фокуса.tabindex="n" (положительное число) — устанавливает
явный порядок, что не рекомендуется для больших динамических списков,
так как требует постоянного обновления индексов.Для работы с элементами ally.js предоставляет функцию:
import {queryFocusable} from 'ally.js';
const focusableElements = queryFocusable({
context: document.getElementById('list-container')
});
Параметры функции queryFocusable:
context — контейнер, в пределах которого ищутся
фокусируемые элементы.includeOnlyTabbable — если true,
возвращает только элементы с положительным tabindex или без
него.filter — пользовательская функция для исключения
элементов по критериям (например, скрытых элементов или disabled).Результат — массив DOM-элементов, готовых к управлению фокусом.
tabindexВ больших списках особенно важно управлять tabindex
динамически, чтобы:
Пример динамического управления:
import {focus} from 'ally.js';
function setActiveItem(index, container) {
const items = queryFocusable({context: container});
items.forEach((el, i) => {
el.tabIndex = i === index ? 0 : -1;
});
focus(items[index]);
}
Пояснения:
tabindex="-1".tabindex="0" и программно
фокусируется через focus().Ally.js упрощает управление событиями клавиатуры и перемещением фокуса:
container.addEventListener('keydown', (event) => {
const items = queryFocusable({context: container});
const currentIndex = items.findIndex(el => el === document.activeElement);
if (event.key === 'ArrowDown') {
setActiveItem((currentIndex + 1) % items.length, container);
event.preventDefault();
} else if (event.key === 'ArrowUp') {
setActiveItem((currentIndex - 1 + items.length) % items.length, container);
event.preventDefault();
}
});
Эта схема обеспечивает:
tabindex только при необходимости.Ally.js поддерживает фильтрацию невидимых и disabled элементов. При
больших списках это предотвращает попадание в tabindex
элементов, которые пользователю не доступны:
const visibleItems = queryFocusable({
context: container,
filter: (el) => el.offsetParent !== null && !el.disabled
});
Преимущества:
Для больших списков, где элементы создаются по мере прокрутки (виртуализация), ally.js позволяет:
queryFocusable
без дополнительных проверок.Пример обновления после рендера:
function updateFocusAfterRender(container, currentIndex) {
const items = queryFocusable({context: container});
const newIndex = Math.min(currentIndex, items.length - 1);
setActiveItem(newIndex, container);
}
При сотнях элементов важно:
queryFocusable с узким
context.tabindex без надобности —
обновлять только активный и предыдущий элемент.Для списков с группами или разделами (например, меню с подменю)
полезно управлять tabindex на уровне контейнеров:
aria-hidden="true" и
tabindex="-1".const activeSectionItems = queryFocusable({context: activeSection});
activeSectionItems.forEach(el => el.tabIndex = 0);
Такой подход предотвращает случайное попадание фокуса на скрытые или свернутые элементы.
Использование ally.js в больших списках позволяет выстраивать
надежную, производительную и доступную навигацию, управляя
tabindex динамически и минимизируя ручное вмешательство в
порядок элементов.