Собственная реализация

Библиотека Tabbable в JavaScript предназначена для определения элементов на странице, которые могут получать фокус при навигации с помощью клавиши Tab. В её основе лежет анализ DOM-структуры и вычисление того, какие элементы являются интерактивными и видимыми. Ключевыми критериями для элемента, чтобы считаться “фокусируемым”, являются:

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


Алгоритм поиска tabbable-элементов

Реализация функции для поиска tabbable-элементов обычно включает несколько шагов:

  1. Получение всех потенциально интерактивных элементов. Для этого можно использовать селекторы:

    const focusableSelectors = `
        a[href]:not([tabindex^="-"]),
        area[href]:not([tabindex^="-"]),
        input:not([disabled]):not([type="hidden"]),
        select:not([disabled]),
        textarea:not([disabled]),
        button:not([disabled]),
        iframe,
        object,
        embed,
        [tabindex]:not([tabindex^="-"]),
        [contenteditable]
    `;
    const candidates = Array.from(document.querySelectorAll(focusableSelectors));

    Такой список включает элементы с естественным фокусом и элементы с явным tabindex.

  2. Фильтрация по видимости. Важно исключить элементы, которые не видны пользователю. Проверка обычно включает:

    • offsetParent не равен null.
    • getClientRects().length > 0.
    • Проверку стилей visibility и display.
    function isVisible(element) {
        return element.offsetParent !== null && element.getClientRects().length > 0;
    }
    const visibleCandidates = candidates.filter(isVisible);
  3. Учет порядка табуляции. Элементы с положительным tabindex имеют приоритет над элементами с tabindex=0. Для корректной последовательности можно использовать сортировку:

    visibleCandidates.sort((a, b) => {
        const tabA = parseInt(a.getAttribute('tabindex') || '0', 10);
        const tabB = parseInt(b.getAttribute('tabindex') || '0', 10);
        return tabA - tabB;
    });
  4. Обработка контекстов с Shadow DOM и фреймами. Для комплексных интерфейсов важно учитывать, что tabbable-элементы могут находиться внутри Shadow DOM или