В библиотеке Shopify Draggable виртуализация элементов применяется для оптимизации работы с большим количеством draggable-объектов, когда непосредственная отрисовка и управление всеми DOM-элементами одновременно становится неэффективной. Виртуализация позволяет поддерживать плавное взаимодействие пользователя с интерфейсом, уменьшая нагрузку на браузер и улучшая отзывчивость.
Основная идея виртуализации заключается в том, чтобы отображать в DOM только те элементы, которые находятся в видимой области, и динамически добавлять или удалять их при прокрутке. В контексте Draggable это особенно важно для списков или сеток с сотнями или тысячами элементов:
Draggable не имеет встроенного полного механизма виртуализации, как специализированные библиотеки (например, React Virtualized), но предоставляет достаточную гибкость для её интеграции.
Основные подходы:
const container = document.querySelector('.draggable-container');
container.style.height = '500px';
container.style.overflow = 'auto';
Это позволяет следить за скроллом и динамически подгружать видимые элементы.
container.addEventListener('scroll', () => {
const scrollTop = container.scrollTop;
const containerHeight = container.clientHeight;
// Рассчитываем индекс первого и последнего видимого элемента
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.ceil((scrollTop + containerHeight) / itemHeight);
renderVisibleItems(startIndex, endIndex);
});
Здесь itemHeight — фиксированная высота элемента списка.
Функция renderVisibleItems отвечает за добавление
DOM-элементов только для диапазона
[startIndex, endIndex].
const draggable = new Draggable(container, {
draggable: '.draggable-item',
delay: 0,
mirror: {
constrainDimensions: true
}
});
При динамической подгрузке элементов необходимо перерегистрировать новые элементы в Draggable, чтобы они становились draggable:
function renderVisibleItems(start, end) {
container.innerHTML = ''; // очищаем предыдущие
for (let i = start; i < end; i++) {
const item = createItem(i);
container.appendChild(item);
}
draggable.destroy(); // удаляем старую инициализацию
draggable.add(container.querySelectorAll('.draggable-item')); // добавляем новые элементы
}
Для больших списков важно избегать перерегистрации Draggable на каждый скролл. Оптимальные методы:
let timeout;
container.addEventListener('scroll', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
updateVisibleItems();
}, 50);
});
const container = document.querySelector('.draggable-container');
const items = Array.from({ length: 1000 }, (_, i) => i);
const itemHeight = 50;
function createItem(index) {
const div = document.createElement('div');
div.className = 'draggable-item';
div.style.height = `${itemHeight}px`;
div.textContent = `Элемент ${index}`;
return div;
}
function renderVisibleItems(start, end) {
container.innerHTML = '';
for (let i = start; i < end; i++) {
container.appendChild(createItem(items[i]));
}
draggable.destroy();
draggable.add(container.querySelectorAll('.draggable-item'));
}
const draggable = new Draggable(container, { draggable: '.draggable-item' });
container.addEventListener('scroll', () => {
const startIndex = Math.floor(container.scrollTop / itemHeight);
const endIndex = startIndex + Math.ceil(container.clientHeight / itemHeight) + 2; // буфер
renderVisibleItems(startIndex, endIndex);
});
renderVisibleItems(0, Math.ceil(container.clientHeight / itemHeight) + 2);
Такой подход обеспечивает плавную работу drag & drop на тысячах элементов, минимизируя нагрузку на DOM и повышая производительность интерфейса.