Обычный <select> и даже базовые реализации
кастомных выпадающих списков начинают деградировать при росте количества
опций до нескольких тысяч и выше. Основная причина — одновременное
создание DOM-узлов для всех элементов списка, постоянные перерасчёты
layout и высокая нагрузка на рендеринг.
При 10 000–50 000 опций браузер тратит ресурсы не на работу пользователя с интерфейсом, а на поддержание огромного DOM-дерева. Это приводит к:
Virtual Scroll решает проблему фундаментально: в DOM присутствует только ограниченное число элементов, соответствующих видимой области.
Virtual Scroll опирается на идею окна (viewport), которое перемещается по логическому массиву данных.
Вместо рендеринга всех элементов:
Ключевой эффект — постоянная сложность O(k), где k — количество видимых элементов, а не O(n), где n — размер списка.
Virtual Scroll включается как плагин:
new TomSelect("#select", {
plugins: ["virtual_scroll"],
maxOptions: 10000,
options: largeDataset
});
Сам факт подключения плагина изменяет стратегию рендеринга списка. Вместо стандартного механизма построения DOM используется буферизированный рендерер.
Virtual Scroll требует, чтобы данные были:
Если используется динамическая подгрузка, виртуальный скролл работает только как слой отображения, а не как механизм получения данных.
Внутри виртуального скролла используется буфер — дополнительный запас элементов до и после видимой области. Это необходимо для:
Буфер задаёт количество дополнительных элементов:
Типичная конфигурация:
new TomSelect("#select", {
plugins: ["virtual_scroll"],
virtualScroll: {
itemHeight: 32,
buffer: 8
}
});
Основной механизм основан на формуле:
startIndex = floor(scrollTop / itemHeight)endIndex = startIndex + visibleCount + bufferПри каждом событии scroll:
scrollTop;Важно, что DOM не расширяется — происходит переиспользование узлов.
Рендеринг в виртуальном скролле строится вокруг переиспользования шаблона элемента. Каждый элемент списка:
Внутренний процесс можно представить так:
renderWindow(items) {
for (let i = 0; i < items.length; i++) {
this.pool[i].update(items[i]);
}
}
Такой подход минимизирует garbage collection и снижает нагрузку на движок JavaScript.
Фильтрация — одна из самых дорогих операций в обычных селектах, но в виртуальном скролле она оптимизируется за счёт:
Пример:
new TomSelect("#select", {
plugins: ["virtual_scroll"],
maxOptions: 50000,
searchField: ["text"],
score: function(search) {
return function(item) {
return item.text.includes(search) ? 1 : 0;
};
}
});
После фильтрации пересоздаётся только активный window, а не весь список.
Virtual Scroll в Tom Select даёт значительное улучшение:
Основной выигрыш наблюдается при n > 5000 элементов.
При резком перемещении скролла возможны:
Для сглаживания применяется:
Virtual Scroll не конфликтует с AJAX-источниками, но изменяет модель загрузки:
Пример гибридного подхода:
new TomSelect("#select", {
plugins: ["virtual_scroll"],
load: function(query, callback) {
fetch(`/api/items?q=${query}`)
.then(res => res.json())
.then(data => callback(data));
}
});
При этом виртуальный скролл работает поверх уже загруженных данных.
Наиболее стабильный режим — фиксированная высота строки. В этом случае:
При динамической высоте:
Внутренние реализации часто предпочитают фиксированную модель как базовую.
Ключевой принцип — минимизация создания новых узлов:
Это снижает нагрузку на garbage collector и уменьшает фризы интерфейса.
При изменении высоты dropdown:
Это важно для адаптивных интерфейсов, где высота зависит от viewport.
Virtual Scroll не решает все проблемы производительности:
Также при слишком маленьком списке (до 100–200 элементов) overhead виртуализации может быть избыточным.
При использовании дополнительных расширений Tom Select важно учитывать порядок выполнения:
Конфликты чаще всего возникают при плагинах, изменяющих высоту или структуру DOM.
Virtual Scroll в рамках Tom Select можно рассматривать как трёхслойную систему:
Именно разделение этих слоёв позволяет обрабатывать десятки тысяч элементов без деградации интерфейса.