Библиотека Focus-trap обеспечивает контроль фокуса внутри определённого DOM-элемента, предотвращая его выход за пределы заданной области. При интеграции с клиентским роутингом в Single Page Application (SPA) возникают особенности управления фокусом при смене маршрутов и динамическом рендеринге компонентов.
Фокус должен быть корректно перемещён при открытии модальных окон, сайдбаров или всплывающих панелей. При переходе между страницами фокус-трап необходимо активировать или деактивировать в зависимости от текущего состояния маршрута. Неправильное управление может привести к «залипанию» фокуса на старых элементах или потере доступности интерфейса.
Для SPA критично использовать ленивую инициализацию фокус-трапа:
import { createFocusTrap } from 'focus-trap';
let modalTrap;
function openModal(modalElement) {
modalTrap = createFocusTrap(modalElement, {
escapeDeactivates: true,
clickOutsideDeactivates: true,
initialFocus: modalElement.querySelector('button') // стартовый фокус
});
modalTrap.activate();
}
function closeModal() {
modalTrap.deactivate();
}
Ключевые моменты:
initialFocus определяет элемент, который получит фокус
при активации.escapeDeactivates позволяет закрыть фокус-трап клавишей
Escape.clickOutsideDeactivates даёт возможность деактивации
кликом вне зоны.В SPA компоненты часто монтируются и размонтируются без перезагрузки страницы. При этом необходимо отслеживать события роутинга, чтобы корректно деактивировать старый фокус-трап:
import { onRouteChange } from './router';
onRouteChange((newRoute) => {
if (modalTrap) {
modalTrap.deactivate();
modalTrap = null;
}
});
Рекомендации:
В React удобно создавать кастомный хук для управления фокус-трапом:
import { useEffect, useRef } from 'react';
import { createFocusTrap } from 'focus-trap';
export function useFocusTrap(active) {
const trapRef = useRef(null);
const focusTrapInstance = useRef(null);
useEffect(() => {
if (trapRef.current && active) {
focusTrapInstance.current = createFocusTrap(trapRef.current, {
escapeDeactivates: true,
clickOutsideDeactivates: true
});
focusTrapInstance.current.activate();
}
return () => {
focusTrapInstance.current?.deactivate();
};
}, [active]);
return trapRef;
}
Компонент может использовать хук следующим образом:
function Modal({ isOpen }) {
const trapRef = useFocusTrap(isOpen);
return (
isOpen && (
<div ref={trapRef} className="modal">
<button>Закрыть</button>
</div>
)
);
}
В Vue можно использовать директиву для управления фокус-трапом при монтировании элемента:
import { createFocusTrap } from 'focus-trap';
export default {
directives: {
focusTrap: {
mounted(el) {
el._focusTrap = createFocusTrap(el, {
escapeDeactivates: true,
clickOutsideDeactivates: true
});
el._focusTrap.activate();
},
unmounted(el) {
el._focusTrap.deactivate();
}
}
}
};
Использование:
<div v-if="isModalOpen" v-focus-trap>
<button @click="closeModal">Закрыть</button>
</div>
Если контент загружается асинхронно после активации трапа, важно обновить список фокусируемых элементов:
modalTrap.updateContainerElements();
Эта функция пересчитывает все элементы, доступные для фокуса, предотвращая ситуации, когда новые кнопки или поля ввода остаются недоступными для клавиатурной навигации.
initialFocus или использовать первый
интерактивный элемент.returnFocusOnDeactivate: true для
возвращения фокуса к элементу, который активировал модальное окно.Focus-trap корректно работает с:
React Router — отслеживание изменения маршрута через
useEffect.Vue Router — использование хуков
beforeRouteLeave для деактивации.Angular Router — через подписку на
Router.events и деактивацию трапа при
NavigationStart.Такой подход обеспечивает непрерывный контроль фокуса и повышает доступность интерфейсов в сложных SPA, где модальные окна, панели и динамические формы могут появляться и исчезать без полной перезагрузки страницы.