Позиционирование за пределами viewport

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

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

Фокусируемыми считаются элементы, у которых:

  • Установлен атрибут tabindex ≥ 0.
  • Элементы форм: <input>, <select>, <textarea>, <button>, <a href>.
  • Элементы, видимые и не скрытые через display: none или visibility: hidden.
  • Элементы, не заблокированные через disabled.

Пример использования базовой функции:

import { tabbable } from 'tabbable';

const container = document.getElementById('form-container');
const focusableElements = tabbable(container);

console.log(focusableElements); 
// Массив всех элементов, на которые можно перейти табом

Обработка элементов за пределами viewport

Часто возникает необходимость учитывать элементы, находящиеся вне видимой области страницы. Tabbable позволяет фильтровать такие элементы, опираясь на их видимость и размеры:

  • Элемент считается за пределами viewport, если его bounding rectangle (getBoundingClientRect) полностью находится за границами окна просмотра.
  • Такие элементы можно исключить из последовательности навигации или обрабатывать отдельно для скроллинга и фокуса.

Пример фильтрации элементов вне viewport:

function isInViewport(element) {
  const rect = element.getBoundingClientRect();
  return (
    rect.top < window.innerHeight &&
    rect.bottom > 0 &&
    rect.left < window.innerWidth &&
    rect.right > 0
  );
}

const allFocusable = tabbable(document.body);
const visibleFocusable = allFocusable.filter(isInViewport);

console.log(visibleFocusable);

Настройка табуляции через опции

Tabbable поддерживает параметры для тонкой настройки:

  • includeContainer — включает сам контейнер в список элементов, если он фокусируемый.
  • displayCheck — функция, определяющая, учитывать ли элемент в последовательности. Можно переопределить стандартную проверку видимости.
  • getShadowRoot — позволяет учитывать элементы внутри shadow DOM.

Пример использования опций:

const focusableElements = tabbable(container, {
  includeContainer: true,
  displayCheck: (node) => {
    // Игнорировать элементы за пределами viewport
    return isInViewport(node);
  }
});

Работа с динамическим контентом

При изменении DOM (добавление/удаление элементов) последовательность табуляции может меняться. Tabbable обеспечивает актуальный список фокусируемых элементов при каждом вызове функции. Рекомендуется вызывать tabbable() при каждом событии, влияющем на структуру страницы:

const observer = new MutationObserver(() => {
  const focusable = tabbable(document.body);
  console.log(focusable);
});

observer.observe(document.body, { childList: true, subtree: true });

Интеграция с пользовательскими компонентами

Для компонентов с динамическим открытием (модальные окна, выпадающие списки) важно:

  • Обновлять список фокусируемых элементов после рендеринга.
  • Исключать из табуляции скрытые контейнеры.
  • Поддерживать фокус на первом элементе при открытии и возвращение на триггер при закрытии.

Пример фокусировки модального окна:

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

openModalButton.addEventListener('click', () => {
  modal.style.display = 'block';
  const focusables = tabbable(modal);
  if (focusables.length) focusables[0].focus();
});

Взаимодействие с CSS и позиционированием

  • Элементы с position: absolute или fixed, которые находятся за пределами viewport, по умолчанию учитываются в табуляции. Использование кастомной функции displayCheck позволяет исключить их.
  • Элементы, скрытые через overflow: hidden или clip-path, можно также исключить, проверяя видимую площадь через getBoundingClientRect().

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

При больших DOM рекомендуется:

  • Ограничивать область поиска контейнером, а не сканировать весь документ.
  • Кэшировать результаты, если структура страницы не меняется.
  • Использовать MutationObserver для обновления только при изменении видимого контента.

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

Особенности работы с фреймами и shadow DOM

  • Tabbable корректно обрабатывает iframe и shadow DOM, если указана функция getShadowRoot.
  • Для сложных вложенных компонентов требуется рекурсивный обход и объединение результатов:
function deepTabbable(root) {
  const nodes = tabbable(root);
  root.querySelectorAll('*').forEach(node => {
    if (node.shadowRoot) nodes.push(...deepTabbable(node.shadowRoot));
  });
  return nodes;
}
  • Такой подход позволяет учитывать все фокусируемые элементы вне зависимости от уровня вложенности и позиции за пределами viewport.

Итоговые рекомендации по позиционированию за пределами viewport

  • Всегда проверять видимость элемента через getBoundingClientRect.
  • Использовать кастомные проверки для исключения элементов, которые находятся за пределами видимой области.
  • Поддерживать актуальный список фокусируемых элементов при динамических изменениях DOM.
  • Для модальных окон и динамических интерфейсов устанавливать фокус на первый доступный элемент и возвращать на триггер при закрытии.

Эти методы обеспечивают корректную и предсказуемую навигацию клавиатурой, соответствующую современным стандартам доступности.