Библиотека Tabbable определяет элементы, которые могут получать фокус при навигации с клавиатуры. Основная задача библиотеки — найти элементы, которые:
focusable)tabbable)Для поиска элементов библиотека активно использует CSS-селекторы и обход DOM-дерева. При работе с большими интерфейсами эффективность этих селекторов напрямую влияет на производительность.
В сложных веб-приложениях DOM может содержать тысячи узлов. Неоптимальные селекторы приводят к избыточным проверкам, дополнительным проходам по дереву и замедлению навигации.
Оптимизация селекторов в контексте Tabbable направлена на:
Tabbable использует список селекторов, соответствующих элементам, потенциально способным принимать фокус.
Примеры таких элементов:
<input><select><textarea><button><a href><audio controls><video controls>tabindexcontenteditableВ библиотеке применяется объединённый селектор:
const candidateSelector = `
input,
select,
textarea,
a[href],
button,
[tabindex],
audio[controls],
video[controls],
[contenteditable]
`;
Этот селектор используется для первоначального поиска элементов через
querySelectorAll.
Дальнейшая логика фильтрует найденные элементы по дополнительным условиям:
disabledtabindexИспользование слишком общих селекторов приводит к избыточному количеству кандидатов.
Пример:
document.querySelectorAll("*")
Такой подход заставляет браузер проверять каждый элемент DOM, после чего выполнять фильтрацию на стороне JavaScript. В больших приложениях это создаёт серьёзную нагрузку.
Tabbable избегает таких селекторов, сразу ограничивая поиск потенциальными кандидатами.
Сложные цепочки увеличивают стоимость поиска.
Неоптимальный пример:
.container div button
Браузеру требуется:
.containerdivbuttonПри большом количестве элементов это замедляет выполнение.
Более эффективный вариант:
.container button
или
.container [tabindex], .container button
Частое выполнение querySelectorAll с одинаковыми
селекторами увеличивает нагрузку.
Неоптимально:
function getFocusable(container) {
return container.querySelectorAll(candidateSelector);
}
Если функция вызывается много раз, каждый вызов запускает новый обход DOM.
Оптимизация заключается в кэшировании результатов или ограничении области поиска.
Одним из наиболее эффективных способов оптимизации является ограничение контейнера, внутри которого выполняется поиск.
Вместо:
document.querySelectorAll(candidateSelector)
используется:
container.querySelectorAll(candidateSelector)
Это уменьшает количество проверяемых элементов.
import { tabbable } from "tabbable";
const modal = document.querySelector(".modal");
const elements = tabbable(modal);
В данном случае библиотека анализирует только элементы внутри модального окна, а не весь документ.
Преимущества:
Вместо одного длинного селектора иногда выгоднее использовать несколько специализированных запросов.
Неоптимальный вариант:
container.querySelectorAll(`
input,
select,
textarea,
button,
a[href],
[tabindex],
[contenteditable]
`)
Альтернативный подход:
const inputs = container.querySelectorAll("input, select, textarea");
const buttons = container.querySelectorAll("button");
const links = container.querySelectorAll("a[href]");
Это может улучшить производительность, если:
Однако Tabbable использует объединённый селектор, поскольку современные браузеры оптимизируют такие запросы.
Часто требуется исключать элементы, которые заведомо не могут быть сфокусированы.
Пример:
button:not([disabled])
или
a[href]:not([tabindex="-1"])
Такая оптимизация переносит часть логики фильтрации на уровень селектора, уменьшая объём работы JavaScript.
Однако чрезмерное использование :not() усложняет
селектор и может снизить производительность.
Баланс достигается сочетанием:
Tabbable использует стратегию:
Это снижает стоимость селектора.
Пример фильтрации:
function isFocusable(node) {
if (node.disabled) return false;
if (node.hidden) return false;
return true;
}
Затем выполняется фильтрация:
const candidates = container.querySelectorAll(candidateSelector);
const focusable = Array.from(candidates).filter(isFocusable);
Такой подход:
Элементы с tabindex требуют дополнительной проверки.
Пример:
<div tabindex="0"></div>
Однако значение tabindex может быть:
Tabbable проверяет:
const tabindex = node.getAttribute("tabindex");
И затем:
if (tabindex !== null && parseInt(tabindex, 10) < 0) {
return false;
}
Чтобы уменьшить количество проверок, селектор уже ограничивает поиск:
[tabindex]
Это позволяет избежать анализа элементов без атрибута.
Скрытые элементы не должны участвовать в табуляции.
Однако определение видимости через CSS-селекторы невозможно полностью. Проверяются свойства:
display: nonevisibility: hiddenhiddeninertTabbable выполняет дополнительные проверки:
function isHidden(node) {
return node.offsetParent === null;
}
Селекторы не решают эту задачу полностью, поэтому важна эффективная фильтрация.
Некоторые проверки могут вызывать reflow, например:
offsetWidthoffsetHeightgetBoundingClientRectЧастые обращения к этим свойствам замедляют работу.
Оптимизация:
В некоторых случаях обход DOM может выполняться через TreeWalker.
Пример:
const walker = document.createTreeWalker(
container,
NodeFilter.SHOW_ELEMENT,
null
);
Этот подход позволяет:
Однако querySelectorAll обычно быстрее благодаря
внутренним оптимизациям браузера.
Селекторы и регулярные проверки могут кэшироваться.
Пример:
const selector = candidateSelector;
или
const candidates = container.querySelectorAll(selector);
Повторное создание строк селекторов увеличивает нагрузку на интерпретатор.
querySelectorAll возвращает NodeList.
Преобразование:
Array.from(nodeList)
выполняет копирование элементов.
В оптимизированном коде:
NodeListПри большом количестве элементов полезна ленивая фильтрация.
Пример:
for (const node of candidates) {
if (isFocusable(node)) {
result.push(node);
}
}
Этот подход:
Эффективная работа селекторов в Tabbable достигается соблюдением нескольких принципов.
Ограничение области поиска
Всегда использовать конкретный контейнер.
Простые селекторы
Избегать длинных и сложных цепочек.
Минимальная фильтрация селектором
Часть логики переносить в JavaScript.
Избегание универсальных селекторов
Не использовать *.
Сокращение DOM-операций
Минимизировать количество вызовов querySelectorAll.
Фильтрация после поиска
Применять дополнительные проверки только к потенциальным кандидатам.
В приложениях с большим DOM оптимизация селекторов влияет на:
Tabbable используется в таких инструментах, как:
Поэтому оптимизация селекторов становится важной частью архитектуры интерфейса.
Грамотное построение селекторов и фильтрации позволяет значительно ускорить определение tabbable-элементов даже в очень больших DOM-структурах.