🚧 Почему виртуальный скролл ломается на больших данных

Сегодня хочу рассказать вам вот об этой статье How to Implement Virtual Scrolling Beyond the Browser's Limit.

Когда мы говорим, что нужно оптимизировать рендеринг большого количества элементов в браузере — чаще всего речь идёт о виртуальном скроллинге.

Обычно он реализуется так: - есть элемент, назовём его viewport, - внутри — контейнер content, в котором находится список всех элементов, - этому контейнеру задаётся высота по формуле: высота одного элемента × общее количество элементов, - при этом рендерятся только те элементы, которые попадают во viewport, а их позиция задаётся через transform: translateY(…).

Но знаете ли вы, что у стандартного virtual scrolling, который использует нативный скроллбар, есть ограничения?

Когда мы задаём общую высоту контента, она может оказаться больше, чем максимально поддерживаемое значение в браузере. Я измерил локально и получил такие значения: - Safari: 33,554,428px - Chrome: 16,777,200px - Firefox: после определённого значения значение сбрасывается в 0

Ограничения можете проверить сами. 👉 Здесь накидал небольшую демку.

Из-за этого нельзя доскроллить до самого конца списка — высота просто "обрезается".

Примеры:

На работе мне нужно было реализовать JsonViewer, который умеет отображать огромные JSON-файлы. <br><br>Я использовал [TanStack Virtual](https://tanstack.com/virtual) для виртуального скроллинга, но столкнулся с описанной выше проблемой — реальная высота контента была в два раза больше, чем поддерживают браузеры. Позже нашел, что в их репозитории есть [ишью по этому поводу](https://github.com/TanStack/virtual/issues/616).<br><br>Чтобы обойти это ограничение, нужно отказаться от нативного скролла и реализовать собственную скролл-панель. В этом случае мы не задаём высоту контента, а сами рассчитываем, какие элементы должны рендериться, исходя из позиции скроллбара.<br><br>Выглядит это примерно так:<br><br>const ITEM_HEIGHT = 30;

// Генерируем список из 3 миллионов элементов const items = Array.from({ length: 3000000 }, (_, i) => Item ${i});

// 3,000,000 × 30px = 90,000,000px (> лимита браузера) const totalHeight = ITEM_HEIGHT * items.length; const viewportSize = 300;

export default function App() { return (

{(scrollPosition) => { // Вычисляем, какие элементы нужно отрендерить const startIndex = Math.floor(scrollPosition / ITEM_HEIGHT); const endIndex = Math.min( Math.ceil((scrollPosition + viewportSize) / ITEM_HEIGHT) + 1, items.length ); const visibleItems = items.slice(startIndex, endIndex);

// Смещение первого видимого элемента const startPosition = startIndex * ITEM_HEIGHT;

return (

{visibleItems.map((item) => (

{item}

))}

); }}

); }

Демку, где рендерятся 3 миллиона элементов, можно посмотреть здесь. Подробнее это всё разбирается в статье.

#JavaScript #Performance #Browser``