Библиотека Tabbable предназначена для определения
элементов на странице, которые могут получать фокус при навигации с
помощью клавиши Tab. При работе с
iframe возникают особенности, связанные с
изолированностью их документа от основного DOM. Важно понимать, что
каждый iframe содержит собственный
document и window, и стандартный вызов
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';
});
Если 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 ограничение, и к его документу получить доступ нельзя. В таких случаях обработка ограничивается родительским доменом.
Для программного управления фокусом можно использовать методы
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();
}
});
postMessage или управление фокусом на
стороне iframe.tabbable(doc.body). Для них
требуется использование метода tabbable(shadowRoot) для
каждого Shadow Root.tabbable() на больших
документах iframe, кэшируя результат.tabindex только при
необходимости, иначе последовательность определяется естественным
DOM-порядком.Этот подход обеспечивает безопасную и предсказуемую навигацию клавишей Tab внутри iframe, полностью учитывая ограничения изоляции и доступности элементов.