В ally.js делегирование событий используется для управления поведением интерактивных элементов без необходимости навешивания отдельных слушателей на каждый элемент. Это особенно важно при работе с динамическим DOM, где элементы могут появляться и исчезать после первоначальной загрузки страницы.
Основная идея делегирования заключается в том, что слушатель события привязывается к родительскому элементу, а обработка конкретного события осуществляется на основе целевого элемента (target). Это позволяет минимизировать количество слушателей и повысить производительность.
Пример базового делегирования в ally.js:
import { tabbable } from 'ally.js';
const container = document.querySelector('#menu');
container.addEventListener('keydown', event => {
const target = event.target;
if (!container.contains(target)) return;
// Обработка только для tabbable элементов
if (tabbable(target).length) {
if (event.key === 'ArrowDown') {
focusNext(target);
} else if (event.key === 'ArrowUp') {
focusPrevious(target);
}
}
});
function focusNext(element) {
const elements = tabbable(container);
const index = elements.indexOf(element);
if (index !== -1 && index < elements.length - 1) {
elements[index + 1].focus();
}
}
function focusPrevious(element) {
const elements = tabbable(container);
const index = elements.indexOf(element);
if (index > 0) {
elements[index - 1].focus();
}
}
Ключевой момент: использование функции tabbable
позволяет точно определить элементы, доступные для навигации с
клавиатуры, что повышает доступность интерфейса.
Композиция обработчиков в ally.js позволяет объединять несколько функций обработки событий, обеспечивая модульность и переиспользуемость кода. Вместо создания одного «гигантского» обработчика события, код делится на мелкие, независимые функции, которые можно комбинировать.
Пример композиции:
function stopPropagation(event) {
event.stopPropagation();
}
function preventDefault(event) {
event.preventDefault();
}
function handleArrowNavigation(container) {
return function(event) {
const target = event.target;
if (!container.contains(target)) return;
const elements = tabbable(container);
const index = elements.indexOf(target);
if (event.key === 'ArrowDown' && index < elements.length - 1) {
elements[index + 1].focus();
} else if (event.key === 'ArrowUp' && index > 0) {
elements[index - 1].focus();
}
};
}
const container = document.querySelector('#menu');
container.addEventListener('keydown', event => {
[stopPropagation, preventDefault, handleArrowNavigation(container)].forEach(fn => fn(event));
});
Такой подход повышает читабельность и поддержку кода, а также позволяет легко изменять или расширять поведение, добавляя новые обработчики в массив функций.
Ally.js предоставляет ряд функций, которые упрощают работу с делегированием:
tabbable(container) — возвращает список элементов,
доступных для фокусировки;focusable(container) — возвращает все элементы, которые
можно сделать активными;nextFocus(element) /
previousFocus(element) — перемещение фокуса в пределах
контейнера;observe.focus(container, callback) — подписка на
события фокуса с автоматическим управлением делегированием.Пример с наблюдением за фокусом:
import { observe } from 'ally.js';
const container = document.querySelector('#menu');
const observer = observe.focus(container, ({ target, type }) => {
if (type === 'focus') {
target.classList.add('focused');
} else if (type === 'blur') {
target.classList.remove('focused');
}
});
// Позднее можно отключить наблюдение
// observer.disengage();
Преимущество использования observe.focus в том, что оно
автоматически учитывает динамически добавленные
элементы, что делает делегирование максимально прозрачным.
Эффективное использование ally.js достигается при совместном применении делегирования и композиции обработчиков:
Пример объединенного подхода:
const container = document.querySelector('#menu');
function handleClick(event) {
if (!container.contains(event.target)) return;
console.log('Clicked:', event.target);
}
function handleKeyboard(event) {
const target = event.target;
if (!container.contains(target)) return;
const elements = tabbable(container);
const index = elements.indexOf(target);
if (event.key === 'ArrowDown') elements[index + 1]?.focus();
if (event.key === 'ArrowUp') elements[index - 1]?.focus();
}
container.addEventListener('click', handleClick);
container.addEventListener('keydown', event => [handleKeyboard].forEach(fn => fn(event)));
Такой подход позволяет легко расширять функциональность, добавляя новые обработчики и наблюдатели, сохраняя при этом чистую структуру кода и поддержку доступности.
disengage или
removeEventListener для предотвращения утечек памяти.Эти принципы позволяют строить гибкие, масштабируемые и доступные интерфейсы, полностью используя возможности ally.js в контексте современного веб-разработки.