Фокус уходит из ловушки

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

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

import { createFocusTrap } from 'focus-trap';

const modal = document.getElementById('modal');
const trap = createFocusTrap(modal, {
  escapeDeactivates: true,
  clickOutsideDeactivates: true
});

trap.activate();

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

  • escapeDeactivates — определяет, можно ли деактивировать ловушку с помощью клавиши Esc.
  • clickOutsideDeactivates — позволяет пользователю закрывать ловушку кликом вне её области.
  • returnFocusOnDeactivate — после деактивации фокус возвращается к элементу, который был активен до активации ловушки.

Сценарии ухода фокуса

Даже с активной ловушкой фокуса, существуют ситуации, когда фокус может покинуть контейнер:

  1. Асинхронное изменение DOM Если элементы внутри ловушки удаляются или заменяются динамически, фокус может «уплыть» на другой элемент вне контейнера. Для предотвращения этого важно использовать updateContainerElements() или реинициализировать ловушку после изменений.

  2. Модальные окна и вложенные ловушки При наличии нескольких модальных окон, активных одновременно, последняя активированная ловушка получает приоритет. Фокус может перейти к предыдущей ловушке, если текущая деактивируется некорректно.

  3. Элементы с display: none или visibility: hidden Если текущий фокус находится на элементе, который скрывается, библиотека автоматически переносит фокус на первый доступный элемент. При отсутствии доступных элементов фокус уходит за пределы контейнера.

  4. Неподдерживаемые элементы Некоторые элементы, такие как <div> без атрибута tabindex, не могут принимать фокус. Если ловушка содержит только такие элементы, фокус будет уходить к родительскому элементу или к body.


Методы управления уходом фокуса

Focus-trap предоставляет API для точного контроля:

  • activate(options) — активирует ловушку с возможностью передачи временных опций.
  • deactivate(options) — отключает ловушку. Опция returnFocus управляет возвратом фокуса.
  • pause() и unpause() — временно приостанавливают управление фокусом, полезно при показе подсказок или всплывающих панелей внутри модального окна.

Пример корректной обработки ухода фокуса:

trap.activate();

// Обновление ловушки после динамических изменений
modal.addEventListener('DOMNodeInserted', () => {
  trap.updateContainerElements();
});

// При закрытии модального окна
document.getElementById('closeButton').addEventListener('click', () => {
  trap.deactivate({ returnFocus: true });
});

Практические рекомендации

  • Всегда указывать returnFocusOnDeactivate, чтобы пользователь не терял ориентир после закрытия модального окна.
  • Проверять наличие фокусируемых элементов внутри контейнера. Если их нет, добавлять скрытый элемент с tabindex="0".
  • Использовать clickOutsideDeactivates осторожно: в сложных интерфейсах это может привести к неожиданному уходу фокуса.
  • Вложенные ловушки следует активировать поочерёдно и деактивировать предыдущие, чтобы избежать конфликтов.

Отладка ухода фокуса

Для выявления причин, по которым фокус покидает ловушку:

  1. Включить логирование событий фокуса:
modal.addEventListener('focusin', e => console.log('Focus in:', e.target));
modal.addEventListener('focusout', e => console.log('Focus out:', e.target));
  1. Проверить, все ли элементы контейнера имеют tabindex или являются интерактивными.
  2. При динамическом изменении DOM — убедиться, что updateContainerElements() вызывается после всех изменений.
  3. Использовать инструменты разработчика браузера для отслеживания активного элемента (document.activeElement).

Особенности поведения в браузерах

  • Safari и iOS иногда некорректно обрабатывают программный фокус на скрытых элементах. Необходимо тестировать ловушки на мобильных устройствах.
  • Firefox может переносить фокус на body, если текущий элемент становится недоступным. Решение — гарантировать наличие хотя бы одного видимого интерактивного элемента.
  • При использовании iframes управление фокусом ограничено рамками документа, поэтому ловушки внутри iframe должны быть изолированы.

Заключение по уходу фокуса

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