SortableJS — это библиотека JavaScript, позволяющая создавать интерактивные списки с перетаскиванием элементов (drag-and-drop). Одной из важных концепций при работе с большими списками является виртуализация, которая позволяет эффективно управлять производительностью при отображении сотен и тысяч элементов.
Виртуализация заключается в том, что на странице одновременно рендерятся только те элементы списка, которые видимы пользователю, а остальные находятся в «виртуальном» состоянии. Это снижает нагрузку на DOM и ускоряет взаимодействие с элементами, включая перетаскивание.
SortableJS из коробки не реализует виртуализацию, но может быть интегрирован с библиотеками виртуализации, такими как Virtual DOM, react-window или vue-virtual-scroller, обеспечивая плавный drag-and-drop даже для больших массивов данных.
Основная задача при интеграции SortableJS с виртуализацией — синхронизация видимых элементов с внутренними данными и корректное отслеживание положения элементов при их перетаскивании.
Ключевые шаги интеграции:
Инициализация Sortable Sortable создается на контейнере, содержащем видимые элементы виртуализированного списка.
import Sortable from 'sortablejs';
const sortable = new Sortable(containerElement, {
animation: 150,
ghostClass: 'sortable-ghost',
onEnd: handleDragEnd
});Обновление позиции данных После завершения
перетаскивания (onEnd) необходимо обновить исходный массив
данных, а затем пересчитать видимые элементы для виртуального
рендера:
function handleDragEnd(evt) {
const { oldIndex, newIndex } = evt;
const movedItem = data.splice(oldIndex, 1)[0];
data.splice(newIndex, 0, movedItem);
virtualList.update(data); // обновление виртуализированного списка
}Синхронизация индексов Виртуализация меняет порядок рендеринга элементов, поэтому индексы в DOM могут не совпадать с индексами в массиве данных. Используется отображение DOM-индекса на индекс данных через карту:
const domToDataIndexMap = new Map();
function renderVisibleItems() {
const visibleItems = virtualList.getVisibleItems();
visibleItems.forEach((item, i) => {
domToDataIndexMap.set(item.domId, item.dataIndex);
});
}Обработка скролла При прокрутке
виртуализированного списка необходимо пересоздавать Sortable на новых
видимых элементах или обновлять позиции. Можно использовать метод
Sortable#destroy перед повторной инициализацией, чтобы
избежать конфликтов:
virtualList.onScroll(() => {
sortable.destroy();
renderVisibleItems();
sortable = new Sortable(containerElement, sortableOptions);
});Ghost-элемент Для визуальной индикации перемещения создается ghost-элемент, который не зависит от DOM-индексов. Это позволяет корректно отображать перетаскиваемый элемент при виртуализации.
ghostClass: 'sortable-ghost'Поддержка placeholder Для корректного отображения места вставки используется placeholder. Он должен соответствовать размеру одного элемента и обновляться при изменении видимых элементов.
Обработка анимаций Анимации сортировки
(animation) должны быть синхронизированы с обновлением DOM
виртуального списка, иначе возможны «скачки» элементов при
прокрутке.
Допустим, используется библиотека react-window.
Интеграция SortableJS может выглядеть следующим образом:
import { FixedSizeList as List } from 'react-window';
import Sortable from 'sortablejs';
function VirtualSortableList({ data }) {
const listRef = useRef();
useEffect(() => {
const container = listRef.current._outerRef;
const sortable = new Sortable(container, {
animation: 150,
ghostClass: 'sortable-ghost',
onEnd: evt => {
const movedItem = data.splice(evt.oldIndex, 1)[0];
data.splice(evt.newIndex, 0, movedItem);
listRef.current.resetAfterIndex(0); // обновление виртуального списка
}
});
return () => sortable.destroy();
}, [data]);
return (
<List
height={500}
itemCount={data.length}
itemSize={50}
width={300}
ref={listRef}
>
{({ index, style }) => (
<div style={style} key={data[index].id}>
{data[index].text}
</div>
)}
</List>
);
}
onEnd.Виртуализация в сочетании с SortableJS позволяет работать с очень большими списками, сохраняя плавность интерфейса. Основная задача — корректная синхронизация DOM с данными и управление ghost/placeholder элементами, а также интеграция с библиотекой виртуализации для динамического рендера. Это обеспечивает стабильное и отзывчивое drag-and-drop взаимодействие даже при тысячах элементов.