Оптимизация для больших DOM-деревьев

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

Библиотека предоставляет интерфейс для создания ловушки фокуса и управления его поведением:

import { createFocusTrap } from 'focus-trap';

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

trap.activate();

Ключевые параметры конфигурации:

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

Принципы оптимизации для больших DOM-деревьев

При работе с массивными структурами DOM стандартные методы поиска интерактивных элементов (querySelectorAll, tabindex) могут вызывать значительные задержки. Для крупных приложений это критично, поэтому focus-trap предлагает методы оптимизации:

  1. Ограничение области поиска элементов

    Вместо сканирования всего документа фокус-ловушка должна работать только с конкретным контейнером:

    const trap = createFocusTrap(document.getElementById('modal'), {
      tabbableOptions: { container: document.getElementById('modal') }
    });

    Это предотвращает перебор всех элементов страницы.

  2. Использование кэширования интерактивных элементов

    Для больших списков или сложных модальных окон повторный поиск интерактивных элементов не нужен. Можно кэшировать элементы при активации ловушки:

    let tabbableElementsCache = [];
    
    const trap = createFocusTrap(modal, {
      onActivate: () => {
        tabbableElementsCache = Array.from(modal.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])'));
      },
      get tabbableElements() {
        return tabbableElementsCache;
      }
    });

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

  3. Отложенная активация

    Если модальное окно или панель содержат тяжёлый контент, активацию ловушки фокуса можно отложить до полной загрузки DOM внутри контейнера:

    window.requestAnimationFrame(() => {
      trap.activate();
    });

    Это позволяет браузеру завершить отрисовку элементов и предотвратить мерцания фокуса.

  4. Минимизация подписки на события

    По умолчанию focus-trap отслеживает события keydown, focusin и mousedown. Для больших деревьев DOM количество таких обработчиков может быть критично. Рекомендуется отключать ненужные опции:

    const trap = createFocusTrap(modal, {
      escapeDeactivates: true,
      clickOutsideDeactivates: false
    });

    Если клики вне модального окна не должны деактивировать ловушку, это снижает нагрузку на обработку событий.

Интеграция с динамическим контентом

Для интерфейсов, где элементы добавляются или удаляются динамически, необходимо обновлять список tabbable-элементов. В focus-trap это делается через updateTabbables():

trap.updateTabbables();

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

Управление фокусом при нескольких ловушках

Если в приложении одновременно могут существовать несколько модальных окон, важно управлять стеком активных ловушек:

  • Активировать только верхнюю ловушку.
  • Деактивировать нижние, чтобы фокус не уходил за пределы текущего модального окна.

Пример управления стеком:

const traps = [];

function openModal(modal) {
  const trap = createFocusTrap(modal);
  traps.push(trap);
  trap.activate();
}

function closeModal() {
  const trap = traps.pop();
  trap.deactivate();
  if (traps.length) {
    traps[traps.length - 1].activate();
  }
}

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

  • Не использовать глобальные селекторы (document.querySelectorAll) в больших контейнерах.
  • Использовать tabbable опцию с конкретным контейнером для ускорения поиска интерактивных элементов.
  • Кэшировать элементы, чтобы не делать повторных запросов DOM.
  • Отключать ненужные события, чтобы снизить нагрузку на обработку клавиатуры и мыши.
  • Отслеживать динамические изменения только там, где это необходимо.
  • Управлять стеком ловушек при работе с несколькими модальными окнами одновременно.

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