Циклические ссылки в DOM

Библиотека Tabbable предназначена для работы с элементами интерфейса, которые могут получать фокус при навигации с помощью клавиши Tab. Она обеспечивает точное определение фокусируемых элементов, учитывая видимость, атрибуты и вложенность в DOM. Это особенно важно для сложных интерфейсов с модальными окнами, вкладками и динамически создаваемыми элементами.

Tabbable не изменяет DOM и не добавляет обработчиков событий по умолчанию; её задача — предоставить разработчику массив элементов, доступных для табуляции в порядке их естественного следования в DOM.


Определение фокусируемых и tabbable элементов

Элементы делятся на два типа:

  • Focusable — любой элемент, который может получить фокус, включая <a> с href, <input>, <button> и элементы с tabindex ≥ 0.
  • Tabbable — элемент, на который можно перемещаться с помощью клавиши Tab. Все tabbable элементы также являются focusable, но не все focusable элементы являются tabbable (например, скрытые элементы).

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

import { tabbable } from 'tabbable';

const container = document.getElementById('modal');
const tabbableElements = tabbable(container);

console.log(tabbableElements);
// [button#submit, input#name, a#help]

Ключевые моменты:

  • tabindex="-1" делает элемент focusable, но он не tabbable.
  • Элементы с display: none или visibility: hidden игнорируются.
  • Элементы с disabled также исключаются из списка tabbable.

Работа с циклическими ссылками в DOM

Циклические ссылки возникают, когда в DOM создаются элементы, которые ссылаются друг на друга через вложенные контейнеры или через динамическое перемещение. Например, модальные окна внутри вкладок, вкладки внутри аккордеонов и так далее. Без правильной обработки это может привести к неожиданным результатам при навигации с Tab, включая зацикливание фокуса или пропуск элементов.

Подход Tabbable:

  1. Рекурсивное обходение DOM Библиотека проверяет все потомки контейнера на предмет tabbable. Если элемент содержит вложенные элементы, они обрабатываются рекурсивно, исключая скрытые и disabled элементы.
  2. Учет tabindex Элементы с положительным tabindex получают приоритет при определении порядка. Это важно, когда циклическая структура может содержать элементы с нестандартной табуляцией.
  3. Фильтрация повторяющихся ссылок В случае циклических структур библиотека гарантирует, что один и тот же элемент не попадет в результирующий массив дважды, предотвращая бесконечное перемещение.

Пример обработки цикла:

const containerA = document.getElementById('tabA');
const containerB = document.getElementById('tabB');

// допустим, containerB вложен в containerA через условный рендеринг
const allTabbable = [
  ...tabbable(containerA),
  ...tabbable(containerB)
].filter((el, index, self) => self.indexOf(el) === index);

console.log(allTabbable);

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

Для интерфейсов с динамическим показом/скрытием элементов важна проверка видимости перед вызовом tabbable. Это позволяет избежать включения невидимых элементов в порядок табуляции.

Пример:

function getVisibleTabbable(modal) {
  const elements = tabbable(modal);
  return elements.filter(el => el.offsetParent !== null);
}

Управление циклическим фокусом

В некоторых случаях требуется зацикливание фокуса внутри модального окна или вкладки. Для этого комбинируют tabbable с обработкой событий клавиш:

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

modal.addEventListener('keydown', (event) => {
  if (event.key === 'Tab') {
    const first = elements[0];
    const last = elements[elements.length - 1];

    if (!event.shiftKey && document.activeElement === last) {
      event.preventDefault();
      first.focus();
    } else if (event.shiftKey && document.activeElement === first) {
      event.preventDefault();
      last.focus();
    }
  }
});

Особенности:

  • Использование shift + Tab позволяет корректно зациклить фокус в обратном порядке.
  • Такой подход предотвращает выход фокуса за пределы модального окна или панели вкладок.

Рекомендации при сложных вложенных структурах

  1. Разделение контейнеров: Каждая логическая область интерфейса (модальное окно, вкладка, аккордеон) должна обрабатываться отдельно.
  2. Фильтрация невидимых элементов: Любой элемент с display: none или скрытый через aria-hidden должен исключаться.
  3. Учет динамического DOM: При добавлении или удалении элементов следует пересчитывать tabbable элементы, чтобы порядок оставался корректным.
  4. Предотвращение повторений: Использование фильтрации по уникальности элементов гарантирует, что циклические ссылки не создадут дублирование фокуса.

Взаимодействие с aria и accessibility

Tabbable корректно учитывает атрибуты ARIA:

  • Элементы с aria-hidden="true" игнорируются.
  • Элементы с role="dialog" или role="tabpanel" могут использоваться для ограничения области фокусировки.

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


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