Динамическое изменение контейнера

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

Фокусировка реализуется через замыкание клавиатурного фокуса. Когда focus-trap активен, нажатие клавиши Tab или Shift+Tab ограничивает перемещение фокуса элементами внутри контейнера.

Создание простого трапа выглядит так:

import { createFocusTrap } from 'focus-trap';

const modal = document.getElementById('modal');

const trap = createFocusTrap(modal, {
  onActivate: () => modal.classList.add('active'),
  onDeactivate: () => modal.classList.remove('active'),
});

trap.activate();

Ключевые параметры:

  • onActivate — вызывается при активации фокус-трапа.
  • onDeactivate — вызывается при деактивации.
  • initialFocus — элемент, который получит фокус при активации.
  • allowOutsideClick — разрешает клики вне контейнера.
  • escapeDeactivates — позволяет закрывать трап по Esc.

Динамическое изменение контейнера

Сценарии, когда контейнер фокусировки меняется во время работы приложения, встречаются часто: динамически подгружаемые модальные окна, смена вкладок, замена содержимого панели. focus-trap не поддерживает прямую смену контейнера после создания трапа. Решение состоит в деактивации старого трапа и создании нового.

Пошаговое управление

  1. Хранение текущего трапа:
let currentTrap = null;

function activateTrap(element) {
  if (currentTrap) currentTrap.deactivate();
  currentTrap = createFocusTrap(element, { escapeDeactivates: true });
  currentTrap.activate();
}
  1. Переключение контейнера:
const modalA = document.getElementById('modalA');
const modalB = document.getElementById('modalB');

activateTrap(modalA);

// Позже требуется переключиться
activateTrap(modalB);

Такой подход гарантирует, что фокус всегда контролируется актуальным контейнером, и предотвращает конфликт нескольких активных трапов.


Работа с динамически добавляемыми элементами

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

const modal = document.getElementById('modal');

async function loadContent() {
  const response = await fetch('/modal-content.html');
  modal.innerHTML = await response.text();
  activateTrap(modal);
}

Использование initialFocus особенно важно для динамических элементов, чтобы фокус устанавливался на конкретный первый интерактивный элемент, а не просто на контейнер.

activateTrap(modal, {
  initialFocus: () => modal.querySelector('input:first-of-type')
});

Перезапуск трапа при изменении содержимого

Иногда внутри контейнера меняются элементы, и требуется обновить список фокусируемых элементов. Для этого применяют метод deactivate с последующей активацией нового трапа на том же контейнере:

function refreshTrap(container) {
  if (currentTrap) currentTrap.deactivate({ returnFocus: false });
  currentTrap = createFocusTrap(container);
  currentTrap.activate();
}

Параметр returnFocus: false предотвращает возврат фокуса к предыдущему элементу, что актуально при смене содержимого без закрытия модального окна.


Управление несколькими контейнерами

В интерфейсах с несколькими модальными окнами важно, чтобы активным оставался только один трап.

function switchTrap(from, to) {
  if (currentTrap) currentTrap.deactivate();
  currentTrap = createFocusTrap(to, {
    onDeactivate: () => from.focus()
  });
  currentTrap.activate();
}

Использование onDeactivate позволяет возвращать фокус на предыдущий контейнер при закрытии модального окна, создавая плавный пользовательский опыт.


Полезные практики

  • Активировать трап после рендера контента. Любые изменения DOM внутри контейнера должны завершиться перед вызовом activate().
  • Не держать несколько активных трапов. Всегда деактивируйте старый трап перед активацией нового.
  • Использовать initialFocus. Это предотвращает потерю фокуса или случайное выделение неинтерактивных элементов.
  • Обрабатывать динамические изменения с deactivate и activate. Это универсальная схема для всех сценариев, где DOM контейнера меняется.

Динамическое управление контейнером в focus-trap позволяет создавать сложные интерфейсы с множеством модальных окон, вкладок или асинхронным контентом, сохраняя полное управление фокусом и предсказуемое поведение клавиатурной навигации.