Как сделать Dran And Drop на примере меню. Как двигать кнопки в любом направлении и смещать их по рядам. Для этого используем React с dndKit.
124 открытий949 показов
Привет!
Недавно передо мной встала задача реализовать меню с возможностью перетаскивания кнопок в разных направлениях и рядах. После исследования доступных библиотек я остановился на DnD Kit. Он предлагает отличную документацию, множество примеров и гибкие возможности кастомизации.
Основной функционал строится на двух хуках:
useDraggable — отвечает за перемещение элемента.
useDroppable — определяет область, куда можно вставить элемент.
Дополнительно можно настроить анимации и сортировку внутри рядов.
UI и подготовка
Скачиваем сами либы
yarn add @dnd-kit/core @dnd-kit/sortable
Настраиваем контекст
Оборачиваем не всё приложение, а только компонент, в котором будет происходить DnD. Добавляем сенсоры, чтобы обрабатывать взаимодействия мыши и тач-устройств.
Кнопка скрывается при перемещении (isDragging), а её положение изменяется через CSS.Transform и transition. useSortable будет следить за ref и событиями юзера, например, нажатие и удержание кнопки на этом блоке. Нужные оттуда пропсы прокинем в кнопку.
Перейдем к компоненту ряда. Сортировку и ряды в массив будем добавлять снаружи в DndRoot, а тут мапим массив кнопок из пропса row: IButton[].
SortableContext выполняет очевидную роль сортировки по заданной стратегии, в данном случае — сортирует только горизонтально. Он сам будет временно менять местами блоки в ряду. Но наше свойство order мы поменяем только в handleDragEnd, когда юзер уже определится с выбором и отпустит нажатие.
Теперь проходимся по всем нашим кнопкам и распределяем их по рядам, опираясь на свойство rowNumber. Также сортируем и в ряду по свойству order. Теперь у нас готов весь ui, и хуки могут обрабатывать dnd.
Переходим к нашим трем обработчикам из рута. Мы уже храним айдишник перетаскиваемого блока в activeDndItemId. Хук useSortable в кнопке сам отслеживает события юзера, так что в полученном параметре active.id у handleDragStart = ({active}: DragStartEvent) будет наш параметр.
В handleDragOver = ({active, over}: DragOverEvent) мы можем проверить, где сейчас находится передвигаемый блок (over), и какой из блоков активный (active). Как помните, в ряду мы использовалиси особый стринг id (`row${i}`), так что тут мы его обрабатывать не будем. Сравниваем, что id блока over отличается от id active. Тогда и сменим поле rowNumber у нашей кнопки.
В handleDragEnd = ({ over }: DragOverEvent) меняем order. Наши сортировочные хуки визуально меняют горизонтальное положение кнопок в ряду. Но когда мы отпустим блок, порядок не сменится. Поэтому в конце мы его фиксируем в стейте.
Но кто же пересчитает rowNumber и order, если мы добавим новые ряды, переместим все кнопки из одного ряда в другой или передвинем последнюю кнопку перед первой в том же ряду?
После каждого изменения ряда нам нужно пересчитывать их порядок в стейте. Чтобы не было таких расхождений:
Так, после перетаскивания кнопок с первоночальных рядов в другие и исчезновения первых, rowNumber меняется с гепом. То же самое нужно сделать для order. Ради эксперимента пишем промт в chatGPT и генерим со 2 раза наши алгоритмы:
export const updateButtonsRowOrder = (
array: IButton[],
id: number,
newOrder: number
) => {
const item = array.find((el) => el.id === id);
if (!item) return array; // Если id не найден, возвращаем массив без изменений
// Убираем элемент из массива и сортируем оставшиеся элементы по order
const filteredArray = array
.filter((el) => el.id !== id)
.sort((a, b) => a.order - b.order);
// Вставляем элемент с новым order в нужную позицию
filteredArray.splice(newOrder - 1, 0, { ...item, order: newOrder });
// Мы просто вставили в массив нужный элемент и тк массив отсортированный, заменим все order на index+1
return filteredArray.map((el, index) => ({ ...el, order: index + 1 }));
};
export const updateButtonsRowNumber = (
array: IButton[],
id: number,
newRowNumber: number
) => {
const item = array.find((el) => el.id === id);
if (!item) return array; // Если id не найден, возвращаем массив без изменений
// Обновляем rowNumber указанного элемента
item.rowNumber = newRowNumber;
// Сортируем массив по rowNumber
const sortedArray = array.sort((a, b) => a.rowNumber - b.rowNumber);
// Пересчитываем rowNumber так, чтобы не было пропусков
const uniqueRowNumbers = [
...new Set(sortedArray.map((el) => el.rowNumber)),
].sort((a, b) => a - b);
const mapping = new Map(
uniqueRowNumbers.map((num, index) => [num, index + 1])
);
// Находим по rowNumber в mapping и берем индекс+1 из mapping
sortedArray.forEach((el) => {
el.rowNumber = mapping.get(el.rowNumber) ?? el.rowNumber;
});
return sortedArray;
};
В конце делаем красивую анимацию при переносе. Мы фактически подставим копию нашей кнопки в специальный рендер от библиотеки, и она ее отрендерит. Для сложных кейсов и плавных анимаций рекомендуется именно такой подход.
Получилось довольно много кода, даже без учёта дополнительных сложностей (обрезки текста, превышающего размер кнопки, или ограничения количества кнопок в ряду).
Также можно добавить возможность вставки элементов над или под любым рядом с помощью нашего специального id row${i} или просто с помощью отдельной кнопки внизу (как показано в видео) для добавления нового ряда и блока в нем. Весь функционал, помимо базовой логики, можно улучшить, задействовав три обработчика в корневом компоненте.
А еще даю ссылку на sandbox с полным кодом из этой статьи.
Спрос на iOS-разработчиков сейчас сильно упал. Что делать: уходить в смежную область, например на Android, искать зарубежную компанию или просто подождать? Рассказывает IT-блогер Алексей Гладков.
Всем привет! Я Ваня Ахлестин, занимаюсь поддержкой и развитием аналитической платформы кластера Search&Recommendations на базе Spark и Hadoop в Авито. Сегодня расскажу, как начать использовать ваш код из Python или PySpark и не тратить много времени дорогих разработчиков.
Ретроперспектива недели на Tproger. Во втором выпуске — Евгений Антонов, старший технический менеджер проектов в Yandex Infrastructure и IT-консультант, рассказывает о реструктуризации, подводных дата-центрах Microsoft и секретах управления IT-командами.