Iframe и их содержимое

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


Получение tabbable элементов внутри iframe

Для работы с элементами внутри iframe необходимо сначала получить доступ к его документу:

const iframe = document.querySelector('iframe');
const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;

После получения iframeDocument можно использовать Tabbable так же, как и для обычного DOM:

import { tabbable } from 'tabbable';

const focusableElements = tabbable(iframeDocument.body);
console.log(focusableElements);

Ключевой момент: Tabbable работает только с видимыми элементами, которые могут получать фокус, такими как <input>, <button>, <a href=""> и элементы с tabindex >= 0.


Особенности видимости и доступности

Внутри iframe также применяются все правила доступности:

  • Элементы с display: none или visibility: hidden исключаются.
  • Элементы с disabled не учитываются.
  • Элементы с отрицательным tabindex (tabindex="-1") исключаются из последовательности табуляции, но доступны программно через focus().

Пример фильтрации видимых и tabbable элементов:

const focusable = tabbable(iframeDocument.body).filter(el => {
  const style = iframeDocument.defaultView.getComputedStyle(el);
  return style.visibility !== 'hidden' && style.display !== 'none';
});

Вложенные iframes

Если iframe содержит другой iframe, необходимо рекурсивно проходить вложенные документы. Tabbable не делает этого автоматически, поэтому каждый уровень документа обрабатывается отдельно:

function getAllTabbable(doc) {
  let elements = tabbable(doc.body);
  const iframes = doc.querySelectorAll('iframe');
  iframes.forEach(frame => {
    try {
      const childDoc = frame.contentDocument || frame.contentWindow.document;
      elements = elements.concat(getAllTabbable(childDoc));
    } catch (e) {
      console.warn('Нет доступа к содержимому iframe из-за политики CORS', frame);
    }
  });
  return elements;
}

const allTabbable = getAllTabbable(document);

Особенность: При доступе к iframe на другом домене возникает CORS ограничение, и к его документу получить доступ нельзя. В таких случаях обработка ограничивается родительским доменом.


Управление фокусом внутри iframe

Для программного управления фокусом можно использовать методы focus() и blur() на tabbable элементах:

const firstFocusable = tabbable(iframeDocument.body)[0];
firstFocusable.focus();

Если требуется циклическая навигация внутри iframe, удобно использовать массив tabbable элементов и отслеживать текущий индекс:

let index = 0;
const focusableElements = tabbable(iframeDocument.body);

iframeDocument.addEventListener('keydown', (e) => {
  if (e.key === 'Tab') {
    e.preventDefault();
    index = e.shiftKey ? index - 1 : index + 1;
    if (index < 0) index = focusableElements.length - 1;
    if (index >= focusableElements.length) index = 0;
    focusableElements[index].focus();
  }
});

Ограничения при работе с Tabbable и iframe

  1. CORS-политика — невозможно получить доступ к содержимому iframe, если он с другого домена. Решения включают передачу сообщений через postMessage или управление фокусом на стороне iframe.
  2. Shadow DOM — элементы внутри Shadow DOM не учитываются напрямую через tabbable(doc.body). Для них требуется использование метода tabbable(shadowRoot) для каждого Shadow Root.
  3. Динамически добавляемые элементы — после вставки нового контента необходимо заново вызывать Tabbable, чтобы обновить список элементов, способных получать фокус.

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

  • Фреймворки и UI-библиотеки, такие как React или Vue, часто используют iframe для изоляции редакторов или виджетов. Для корректной навигации клавишей Tab необходимо вызывать Tabbable после полной отрисовки содержимого iframe.
  • Для сложных интерфейсов, содержащих несколько iframe, лучше строить глобальный список tabbable элементов с рекурсивным обходом, чтобы обеспечить предсказуемую последовательность фокусировки.

Рекомендации по оптимизации

  • Минимизировать частые вызовы tabbable() на больших документах iframe, кэшируя результат.
  • Использовать делегирование событий для управления навигацией, чтобы не создавать лишние обработчики на каждом элементе.
  • Контролировать порядок фокуса через tabindex только при необходимости, иначе последовательность определяется естественным DOM-порядком.

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