Виртуализация списков — это подход к оптимизации рендеринга больших массивов данных в веб-приложениях, при котором на страницу отображается только часть элементов, находящихся в видимой области, а остальные создаются и удаляются по мере необходимости. SkateJS как библиотека для создания веб-компонентов предоставляет гибкие инструменты для реализации такой функциональности с использованием реактивных свойств и жизненного цикла компонентов.
Для начала необходимо определить собственный элемент с SkateJS:
import { component, h, prop } from 'skatejs';
class VirtualList extends HTMLElement {
static props = {
items: prop.array(),
itemHeight: prop.number({ default: 30 }),
visibleCount: prop.number({ default: 10 }),
scrollTop: prop.number({ default: 0 })
};
render() {
const start = Math.floor(this.scrollTop / this.itemHeight);
const end = start + this.visibleCount;
const visibleItems = this.items.slice(start, end);
const paddingTop = start * this.itemHeight;
const paddingBottom = (this.items.length - end) * this.itemHeight;
return h('div', { style: { overflowY: 'auto', height: `${this.itemHeight * this.visibleCount}px` },
onScroll: (e) => this.scrollTop = e.target.scrollTop
}, [
h('div', { style: { paddingTop: `${paddingTop}px`, paddingBottom: `${paddingBottom}px` } },
visibleItems.map(item => h('div', { style: { height: `${this.itemHeight}px` } }, item))
)
]);
}
}
component(VirtualList);
Ключевые моменты:
scrollTop отслеживает текущую позицию
прокрутки.start и end.SkateJS позволяет автоматически обновлять отображение при изменении
массива items или позиции прокрутки. Свойства с типом
prop.array() и prop.number() поддерживают
реактивное обновление. Это означает, что при изменении
this.items компонент перерендерится без необходимости
вручную вызывать методы обновления DOM.
this.items.push('Новый элемент');
После выполнения этой операции список пересчитается и отобразит новые элементы в видимой области.
Для больших массивов данных важно минимизировать количество
создаваемых DOM-элементов. Использование виртуализации позволяет
создавать только visibleCount элементов,
что снижает нагрузку на браузер.
Дополнительно можно использовать requestAnimationFrame
для плавного обновления при прокрутке:
onScroll: (e) => {
requestAnimationFrame(() => this.scrollTop = e.target.scrollTop);
}
Это предотвращает чрезмерные вызовы рендера при быстром скролле.
Каждый элемент списка можно стилизовать независимо через inline-стили или CSS-классы. Также можно создавать более сложные шаблоны элементов, включая вложенные компоненты:
visibleItems.map(item => h('list-item', { item }));
При этом list-item может быть отдельным
SkateJS-компонентом с собственной логикой и жизненным циклом.
В некоторых случаях элементы списка могут иметь разную высоту. Для этого требуется хранить массив высот и суммарные отступы:
const start = this.calculateStartIndex(this.scrollTop);
const end = this.calculateEndIndex(this.scrollTop);
Методы calculateStartIndex и
calculateEndIndex определяют, какие элементы должны быть
видимы, учитывая высоту каждого элемента. Это более сложная, но гибкая
схема, позволяющая виртуализировать динамические списки.
SkateJS легко интегрируется с асинхронными источниками данных, такими
как API. Например, при загрузке следующей порции элементов можно
обновлять массив items:
async fetchMoreItems() {
const newItems = await fetch('/api/items').then(res => res.json());
this.items = [...this.items, ...newItems];
}
Компонент автоматически отрендерит новые видимые элементы без потери производительности.
SkateJS предоставляет методы жизненного цикла, которые полезны при виртуализации:
connectedCallback() — инициализация обработчиков
скролла.disconnectedCallback() — удаление подписок для
предотвращения утечек памяти.updated() — обработка изменений items или
других свойств, например пересчет отступов.Эти техники позволяют создавать масштабируемые, отзывчивые интерфейсы даже при работе с тысячами элементов, сохраняя плавность прокрутки и минимальную нагрузку на DOM.