Virtual Scroll

Проблема производительности при больших наборах данных

Обычный <select> и даже базовые реализации кастомных выпадающих списков начинают деградировать при росте количества опций до нескольких тысяч и выше. Основная причина — одновременное создание DOM-узлов для всех элементов списка, постоянные перерасчёты layout и высокая нагрузка на рендеринг.

При 10 000–50 000 опций браузер тратит ресурсы не на работу пользователя с интерфейсом, а на поддержание огромного DOM-дерева. Это приводит к:

  • задержке открытия списка;
  • лагам при прокрутке;
  • росту потребления памяти;
  • блокировке основного потока при фильтрации.

Virtual Scroll решает проблему фундаментально: в DOM присутствует только ограниченное число элементов, соответствующих видимой области.


Принцип работы виртуального скролла

Virtual Scroll опирается на идею окна (viewport), которое перемещается по логическому массиву данных.

Вместо рендеринга всех элементов:

  1. рассчитывается высота одного элемента или используется фиксированная оценка;
  2. вычисляется индекс первого видимого элемента;
  3. рендерится только диапазон элементов вокруг текущего viewport;
  4. при прокрутке DOM обновляется, но не расширяется.

Ключевой эффект — постоянная сложность O(k), где k — количество видимых элементов, а не O(n), где n — размер списка.


Подключение virtual scroll в Tom Select

Virtual Scroll включается как плагин:

new TomSelect("#select", {
  plugins: ["virtual_scroll"],
  maxOptions: 10000,
  options: largeDataset
});

Сам факт подключения плагина изменяет стратегию рендеринга списка. Вместо стандартного механизма построения DOM используется буферизированный рендерер.


Особенности и ограничения данных

Virtual Scroll требует, чтобы данные были:

  • плоскими (flat structure);
  • предсказуемыми по высоте (или с минимальной вариативностью);
  • доступными по индексу без сложной асинхронной логики.

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


Механика буферизации

Внутри виртуального скролла используется буфер — дополнительный запас элементов до и после видимой области. Это необходимо для:

  • плавности прокрутки;
  • предотвращения «пустых зон» при быстром скролле;
  • компенсации задержек рендера.

Буфер задаёт количество дополнительных элементов:

  • сверху viewport;
  • снизу viewport.

Типичная конфигурация:

new TomSelect("#select", {
  plugins: ["virtual_scroll"],
  virtualScroll: {
    itemHeight: 32,
    buffer: 8
  }
});

Расчёт позиции и индексации

Основной механизм основан на формуле:

  • startIndex = floor(scrollTop / itemHeight)
  • endIndex = startIndex + visibleCount + buffer

При каждом событии scroll:

  1. читается scrollTop;
  2. пересчитывается диапазон индексов;
  3. заменяется содержимое контейнера.

Важно, что DOM не расширяется — происходит переиспользование узлов.


Рендеринг элементов

Рендеринг в виртуальном скролле строится вокруг переиспользования шаблона элемента. Каждый элемент списка:

  • создаётся один раз;
  • обновляется при изменении диапазона;
  • не уничтожается полностью при выходе из viewport.

Внутренний процесс можно представить так:

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 даёт значительное улучшение:

  • DOM nodes: O(k) вместо O(n);
  • memory footprint: стабильно низкий;
  • time-to-interactive: уменьшается кратно при больших списках;
  • scroll performance: стабилизируется на 60 FPS при корректной настройке.

Основной выигрыш наблюдается при n > 5000 элементов.


Особенности поведения при быстрых скроллах

При резком перемещении скролла возможны:

  • временные пропуски отрисовки;
  • визуальные «скачки»;
  • догрузка элементов после стабилизации scrollTop.

Для сглаживания применяется:

  • throttling scroll event;
  • requestAnimationFrame для рендера;
  • буферизация заранее рассчитанных индексов.

Интеграция с асинхронной загрузкой

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));
  }
});

При этом виртуальный скролл работает поверх уже загруженных данных.


Работа с фиксированной и динамической высотой элементов

Наиболее стабильный режим — фиксированная высота строки. В этом случае:

  • расчёт индекса упрощается;
  • нет необходимости измерять DOM;
  • исключаются пересчёты layout.

При динамической высоте:

  • требуется измерение элементов;
  • увеличивается стоимость scroll handler;
  • возможны рассинхронизации позиции.

Внутренние реализации часто предпочитают фиксированную модель как базовую.


Оптимизация памяти и повторное использование DOM

Ключевой принцип — минимизация создания новых узлов:

  • элементы не удаляются, а перерабатываются;
  • используется пул DOM-элементов;
  • текстовые значения обновляются без пересоздания узла.

Это снижает нагрузку на garbage collector и уменьшает фризы интерфейса.


Поведение при изменении размера контейнера

При изменении высоты dropdown:

  • пересчитывается visibleCount;
  • обновляется буфер;
  • корректируется startIndex без полной перерисовки.

Это важно для адаптивных интерфейсов, где высота зависит от viewport.


Ограничения и крайние случаи

Virtual Scroll не решает все проблемы производительности:

  • плохо работает с тяжёлыми DOM-элементами (сложные шаблоны);
  • не оптимизирует медленные API-запросы;
  • чувствителен к нестабильной высоте строк;
  • может давать визуальные артефакты при нестандартной стилизации.

Также при слишком маленьком списке (до 100–200 элементов) overhead виртуализации может быть избыточным.


Поведенческая модель при комбинировании с другими плагинами

При использовании дополнительных расширений Tom Select важно учитывать порядок выполнения:

  • плагины фильтрации влияют на dataset до рендера;
  • плагины кастомного рендера влияют на item template;
  • virtual scroll всегда находится на уровне rendering layer.

Конфликты чаще всего возникают при плагинах, изменяющих высоту или структуру DOM.


Итоговая архитектурная модель

Virtual Scroll в рамках Tom Select можно рассматривать как трёхслойную систему:

  1. Data layer — исходный массив или удалённый источник;
  2. Windowing layer — вычисление диапазона видимых элементов;
  3. Render layer — переиспользование DOM и отображение окна.

Именно разделение этих слоёв позволяет обрабатывать десятки тысяч элементов без деградации интерфейса.