Интеграция с роутингом

Библиотека Focus-trap обеспечивает контроль фокуса внутри определённого DOM-элемента, предотвращая его выход за пределы заданной области. При интеграции с клиентским роутингом в Single Page Application (SPA) возникают особенности управления фокусом при смене маршрутов и динамическом рендеринге компонентов.

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

Инициализация Focus-trap при динамических изменениях DOM

Для 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 даёт возможность деактивации кликом вне зоны.

Управление Focus-trap при смене маршрутов

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

import { onRouteChange } from './router';

onRouteChange((newRoute) => {
  if (modalTrap) {
    modalTrap.deactivate();
    modalTrap = null;
  }
});

Рекомендации:

  • Каждый динамический элемент с фокус-трапом должен управлять своим собственным экземпляром.
  • Деактивация старого трапа при смене маршрута предотвращает потерю клавиатурной навигации.

Интеграция с фреймворками

React

В 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

В 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 для возвращения фокуса к элементу, который активировал модальное окно.
  • Проверять клавиатурную навигацию после динамического обновления DOM.

Совместимость с библиотеками роутинга

Focus-trap корректно работает с:

  • React Router — отслеживание изменения маршрута через useEffect.
  • Vue Router — использование хуков beforeRouteLeave для деактивации.
  • Angular Router — через подписку на Router.events и деактивацию трапа при NavigationStart.

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