Сервис развивается: тестируем формат, собираем идеи, улучшаем сервис. Есть идеи?

Написать
Войти
Дайджесты новостей
Сравнение производительности рендеринга элементов в DOM через CSS content-visibility и виртуализацию

Рендеринг больших списков в вебе: системное сравнение CSS content-visibility и виртуализации DOM

Отрисовка многотысячных списков требует выбора между свойством content-visibility: auto и виртуализацией DOM. Разбор архитектурных компромиссов показывает, почему пропуск рендеринга внеэкранных блоков сохраняет нативный поиск и фокус, тогда как виртуализация экономит память ценой усложнения кода.

Рендеринг больших списков в вебе: системное сравнение CSS content-visibility и виртуализации DOM

Отображение многотысячных наборов данных — каталогов товаров, лент сообщений, таблиц аналитики и системных логов — неизбежно сталкивает фронтенд-разработчиков с проблемой снижения отзывчивости интерфейса. Когда страница пытается одновременно отобразить тысячи сложных карточек, браузер начинает пропускать кадры при прокрутке, зависает при первичной загрузке и потребляет сотни мегабайт оперативной памяти.

Для решения этой проблемы в современной веб-разработке применяются два принципиально разных подхода: декларативная оптимизация с помощью нативного CSS-свойства content-visibility и скриптовая виртуализация дерева DOM (механизм Windowing). Выбор между ними — это не просто сравнение производительности, а поиск баланса между сложностью архитектуры приложения и сохранением нативных возможностей браузера.

Причины деградации производительности: раскладка против памяти

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

  1. Дерево DOM (Document Object Model) — объектное представление структуры документа в оперативной памяти. Наряду с DOM браузер параллельно строит дерево доступности (Accessibility Tree) для скринридеров и вспомогательных технологий.
  2. Процессы Layout и Paint — этапы вычисления геометрических размеров каждого элемента на экране (Layout) и последующей отрисовки пикселей в слои графического процессора (Paint).

Когда приложение рендерит обычный список из 10 000 карточек без оптимизаций, основное время тратится на расчет геометрии и перерисовку блоков, которые пользователь в данный момент даже не видит. Однако если каждая карточка содержит десяток вложенных элементов (иконки, кнопки, текстовые поля), то 50 000 карточек создают свыше 550 000 узлов в памяти. В такой ситуации главным узким местом становится уже сам объем дерева элементов и расход оперативной памяти.

Путь CSS: как работает content-visibility и плейсхолдеры размеров

Свойство content-visibility: auto, описанное в спецификации CSS Containment, позволяет браузеру пропускать расчет раскладки и отрисовку внеэкранного содержимого, пока пользователь не приблизится к нему при прокрутке.

Ключевая особенность этого подхода: элементы продолжают физически существовать в дереве DOM, но браузер временно изолирует их рендеринг. Чтобы страница не схлопывалась в нулевую высоту при пропуске невидимых блоков, используется парное свойство contain-intrinsic-size:

.list-item {
  content-visibility: auto;
  contain-intrinsic-size: auto 120px;
}

Конструкция auto <length> задает начальную оценочную высоту блока (например, 120 пикселей). Как только пользователь доскролливает до карточки и браузер выполняет ее реальную отрисовку, движок запоминает точный фактический размер элемента и использует его при последующих вычислениях. Это предотвращает скачки полосы прокрутки (Layout Shifts).

Официальная документация MDN content-visibility и MDN contain-intrinsic-size подчеркивает главные преимущества CSS-маршрута:

  • Нативный поиск по странице (Ctrl/Cmd+F): поскольку текст присутствует в DOM, встроенный поиск браузера находит совпадения и автоматически прокручивает страницу к нужному блоку.
  • Прямые якорные ссылки: переход по URL с фрагментом #item-500 нативно фокусирует и отображает целевой элемент.
  • Доступность: структура документа остается целостной для скринридеров и навигации клавишей Tab.
  • Сохранение состояния: открытые интерактивные спойлеры <details>, выделение текста и фокус ввода не сбрасываются при скролле.

При этом разработчикам необходимо учитывать особенности поддержки в различных браузерных движках (например, исторические задержки синхронизации поиска в Safari) и обязательно тестировать стабильность скролла при карточках с сильно различающейся высотой.

Путь виртуализации: экономия памяти и исчезновение узлов

Виртуализация списков (реализуемая через библиотеки вроде @lit-labs/virtualizer, TanStack Virtual или react-window) действует иначе: она физически удаляет из дерева DOM все узлы, находящиеся за пределами экрана.

Виртуализатор отслеживает позицию прокрутки и монтирует в DOM только элементы, попадающие в видимую область просмотра (viewport), плюс небольшой буфер вокруг нее (overscan, обычно около 800–1000 пикселей). При скролле карточки, выходящие за пределы буфера, уничтожаются (unmount), а новые узлы создаются или переиспользуются.

В результате список из 100 000 записей потребляет в памяти ресурсы всего для нескольких десятков реальных DOM-элементов. Время первой отрисовки держится на уровне долей миллисекунды независимо от общего размера массива данных.

Однако физическое отсутствие узлов в документе разрушает базовые контракты браузера:

  • Текст вне экрана не находится стандартным поиском браузера.
  • Запрос document.getElementById('item-500') возвращает null.
  • Фокус при перемещении по клавише Tab обрывается на границе видимой зоны.
  • Любое неконтролируемое состояние внутри компонента (введенный текст в инпуте, сдвиг курсора, прогресс воспроизведения медиафайла) безвозвратно теряется при демонтаже узла.

Ответственность приложения и доступность интерфейса

Распространенный миф гласит, что виртуализация якобы «ломает доступность». В действительности смонтированные в окне просмотра строки остаются семантически корректными. Однако виртуализация перекладывает всю ответственность за навигационные сценарии с браузера на код приложения:

  1. Поиск по данным: приложение обязано реализовать собственный поисковый интерфейс по исходной модели данных в памяти, рассчитывать индекс найденного элемента и отдавать команду виртуализатору на перемещение к нему.
  2. Асинхронные глубокие ссылки: переход к элементу требует программного вызова API виртуализатора, ожидания завершения пересчета раскладки через ResizeObserver, монтирования узла в DOM и явного вызова метода .focus(). При этом обязательно настраивается защитный таймаут на случай срыва анимации.
  3. Вынесение состояния в глобальную модель: все флаги активности, выбор чекбоксов и раскрытие аккордеонов должны храниться во внешнем хранилище состояния, а не в локальном DOM-узле.
  4. Стабильные ключи рендеринга: привязка карточек к числовым индексам массива недопустима, так как при сортировке или фильтрации это приводит к смешиванию состояний между разными сущностями.

Анализ бенчмарков и границы применимости

Инженерные замеры на синтетических наборах данных (100, 1 000 и 10 000 элементов) наглядно иллюстрируют различия:

  • На 1 000 карточек content-visibility: auto ускоряет первичную отрисовку в несколько раз по сравнению с обычным списком, снижая нагрузку на процессор без усложнения кодовой базы.
  • На 10 000 и более элементов время создания узлов для CSS-маршрута начинает расти линейно, так как память тратится на сотни тысяч элементов. Виртуализация же сохраняет стабильную скорость рендеринга менее 1 миллисекунды.

При этом жесткого порога по количеству строк не существует: 500 сложных интерактивных карточек с графиками и анимациями могут создавать большую нагрузку, чем 10 000 простых текстовых строк.

Практическая матрица выбора

При выборе архитектурного решения рекомендуется опираться на дерево решений:

  • Выбирайте CSS content-visibility: auto, если вы разрабатываете каталог товаров, ленту статей или страницу с умеренным объемом данных (до нескольких тысяч элементов), где критически важен нативный поиск браузера, прямые якорные ссылки, простота разметки и надежная работа вспомогательных технологий без написания сложного связующего кода.
  • Выбирайте виртуализацию DOM, если профилирование в DevTools фиксирует критический дефицит памяти из-за гигантских коллекций (десятки тысяч строк, тяжелые таблицы логов, бесконечные data-grid), а команда готова спроектировать и поддерживать поиск по данным, программное управление фокусом и внешнее хранение состояний компонентов.

Грамотная оптимизация начинается с замеров на самых медленных целевых устройствах пользователей: это позволяет выбрать минимально необходимый уровень сложности архитектуры для достижения стабильных 60 кадров в секунду.