Очистка ресурсов

Библиотека Focus-trap предназначена для управления фокусом в веб-приложениях. Основная задача — ограничить навигацию по элементам интерфейса определённой областью, например, модальными окнами, всплывающими меню или диалогами подтверждения. Она предотвращает выход фокуса за пределы контейнера, что улучшает доступность и предотвращает случайные действия пользователем.

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

Создание и активация ловушки фокуса

Создание фокуса начинается с вызова функции createFocusTrap:

import { createFocusTrap } from 'focus-trap';

const modal = document.getElementById('modal');
const focusTrap = createFocusTrap(modal, {
  escapeDeactivates: true,
  clickOutsideDeactivates: true,
  initialFocus: '#modal input:first-child'
});

Параметры конфигурации:

  • escapeDeactivates – при нажатии клавиши Escape ловушка отключается.
  • clickOutsideDeactivates – щелчок вне контейнера деактивирует ловушку.
  • initialFocus – элемент, который получает фокус при активации.

Активация производится методом activate():

focusTrap.activate();

Деактивация и очистка ресурсов выполняется через deactivate():

focusTrap.deactivate();

Управление ресурсами при деактивации

Deactivation в Focus-trap не только возвращает фокус на предыдущий элемент, но и освобождает внутренние слушатели событий, предотвращая утечки памяти. Важно понимать, что каждый экземпляр ловушки создаёт несколько обработчиков:

  • keydown для отслеживания Tab и Shift+Tab.
  • focusin для удержания фокуса внутри контейнера.
  • click для контроля кликов вне контейнера.

Если экземпляры не деактивируются, эти обработчики остаются активными, что может привести к накоплению обработчиков и снижению производительности.

Варианты очистки ресурсов

  1. Стандартная деактивация Метод deactivate() снимает все внутренние обработчики и восстанавливает исходный фокус. Этот способ подходит для большинства случаев.

  2. Динамическое уничтожение Если контейнер удаляется из DOM до деактивации ловушки, необходимо вызвать deactivate({ returnFocus: false }) для предотвращения ошибок. Это гарантирует, что обработчики будут удалены, даже если фокус невозможно вернуть.

  3. Обработка нескольких ловушек Для сложных интерфейсов с несколькими модальными окнами или вкладками рекомендуется отключать предыдущую ловушку перед активацией новой:

if (activeTrap) {
  activeTrap.deactivate();
}
activeTrap = createFocusTrap(newModal).activate();
  1. Автоматическое удаление В некоторых реализациях используется MutationObserver для наблюдения за удалением контейнера. В момент удаления вызывается deactivate, что предотвращает оставшиеся слушатели.

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

Focus-trap интенсивно использует события focusin и keydown. Для больших приложений с множеством динамических элементов важно:

  • Создавать ловушку только при необходимости.
  • Деактивировать все неиспользуемые экземпляры.
  • Использовать опцию fallbackFocus для обработки случаев, когда нет фокусируемых элементов, чтобы избежать лишних циклов проверки.
const focusTrap = createFocusTrap(modal, {
  fallbackFocus: modal
});

Продвинутые сценарии

  1. Вложенные ловушки Внутри модальных окон могут быть подменю, требующие своей ловушки. В таком случае верхняя ловушка должна быть временно деактивирована, чтобы не блокировать фокус:
parentTrap.deactivate();
childTrap.activate();
childTrap.deactivate();
parentTrap.activate();
  1. Анимации и задержки Если модальное окно появляется с анимацией, активацию ловушки можно отложить с помощью setTimeout или события окончания анимации, чтобы избежать конфликта с первоначальной загрузкой DOM.

  2. Интеграция с React или Vue В реактивных фреймворках важно вызывать deactivate() в момент размонтирования компонента (componentWillUnmount или onBeforeUnmount) для предотвращения утечек памяти.

Важные практики очистки

  • Каждый createFocusTrap должен иметь одного владельца, отвечающего за активацию и деактивацию.
  • Любая динамически удаляемая область интерфейса требует предварительного вызова deactivate.
  • Проверка активных ловушек через focusTrap.active помогает контролировать текущее состояние и предотвращает множественные активные экземпляры.
  • Использование параметров onActivate и onDeactivate позволяет выполнять дополнительные действия при управлении ресурсами.
const trap = createFocusTrap(modal, {
  onActivate: () => console.log('Ловушка активирована'),
  onDeactivate: () => console.log('Ловушка деактивирована')
});

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