Основное введение 📝
- На веб-странице, если вам нужно изменить положение нескольких элементов, вы можете сделать это, перетаскивая элементы. В HTML5 добавлен глобальный атрибут
draggable
, установив для этого свойства значениеtrue/false
чтобы контролировать, можно ли перетаскивать элемент. нужно знать, это:- Ссылки и изображения по умолчанию можно перетаскивать.
draggable
Установить какfalse
Сделайте их неперетаскиваемыми элементами. - Internet Explorer 8 и более ранние версии IE не поддерживаются
draggable
Атрибуты.
- Ссылки и изображения по умолчанию можно перетаскивать.
- Реализация сортировки изображений перетаскиванием в этой статье в основном использует элементы, которые будут срабатывать в процессе перетаскивания.
ondragstart
,ondragend
иondragover
События, конкретное время запуска и другие события могут относиться кMDN, из-за недостатка места в этой статье не будет подробностей.
Демонстрация эффекта 🤩
- Первый взгляд на эффект:
- Идея и конкретный код реализации будут представлены ниже.
Анализ мыслей 🤔
- Реализация сортировки изображений перетаскиванием в этой статье в основном использует элементы, которые будут срабатывать в процессе перетаскивания.
ondragstart
,ondragend
иondragover
событие. - слушая
ondragstart
Event, сохраните данные перетаскиваемого в данный момент элемента и добавьте к нему специальные стили (установите прозрачность элемента и увеличьте элемент). - слушая
ondragend
событие удаляет специальный стиль, установленный на предыдущем шаге, для перетаскиваемого элемента. Чтобы уменьшить потребление памяти, мы помещаем перетаскиваемый элементondragend
Делегирование событий в самый внешний контейнер (делегирование событий) - Реализовать самую важную функцию сортировки перетаскиванием, в основном для привязки элементов
ondragover
событие. когдаondragover
Когда событие срабатывает, вам нужно получить текущую позицию мыши (event.clientX
,event.clientY
), рассчитать, какой элемент перетаскивается мышью, и реализовать обмен и сортировку элементов, оценивая положение текущего перетаскиваемого элемента и других элементов. - В следующих подразделах будет представлен конкретный код реализации.
Конкретная реализация 🧐
базовая компоновка
- Перед реализацией функции перетаскивания выполните базовую компоновку:
/** index.tsx */
import React, { useMemo, useState } from 'react';
import styles from './index.module.scss';
// 每行多少列
const COLUMN = 4;
// 每个元素宽度
const WIDTH = 120;
// 每个元素高度
const HEIGHT = 80;
// 图片左右 padding
const IMAGE_PADDING = 5;
const showList = [
{
id: 2,
name: 'osmo pocket',
image:
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605703865983&di=a35a43a3b9e866f1ee0048563bfd2577&imgtype=0&src=http%3A%2F%2Fpic.rmb.bdstatic.com%2F5d8f2523322e3f4de91021701e95182c.jpeg',
},
{
id: 4,
name: 'mavic pro',
image:
'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=787082346,3090178555&fm=15&gp=0.jpg',
},
{
id: 1,
name: 'mavic mini2',
image:
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605703111703&di=59fa621eb1e7f8f4285b95df80e11fd0&imgtype=0&src=http%3A%2F%2Fp1.itc.cn%2Fimages01%2F20201105%2F600892c32d524b99a118ea56cdf3c211.png',
},
{
id: 3,
name: '机甲大师s1',
image:
'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605703133913&di=a415583ce97dd0a34efe17cac24a97ab&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20200325%2F64ebb68f1125450f91e64bb34dc19d55.jpeg',
},
{
id: 0,
name: 'mavic 2',
image:
'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=4132295553,3011440949&fm=26&gp=0.jpg',
},
];
const DragAndDropPage: React.FC = () => {
const [list, setList] = useState(showList);
// IMPORTANT: 动画需要,需要保持一定的渲染顺序
const sortedList = useMemo(() => {
return list.slice().sort((a, b) => {
return a.id - b.id;
});
}, [list]);
const listHeight = useMemo(() => {
const size = list.length;
return Math.ceil(size / COLUMN) * HEIGHT;
}, [list]);
return (
<div
className={styles.wrapper}
style={{ width: COLUMN * (WIDTH + IMAGE_PADDING) + IMAGE_PADDING }}
>
<ul className={styles.list} style={{ height: listHeight }}>
{sortedList.map((item) => {
const index = list.findIndex((i) => i === item);
const row = Math.floor(index / COLUMN);
const col = index % COLUMN;
return (
<li
key={item.id}
className={styles.item}
style={{
height: HEIGHT,
left: col * (WIDTH + IMAGE_PADDING),
top: row * HEIGHT,
padding: `0 ${IMAGE_PADDING}px`
}}
data-id={item.id}
>
<img src={item.image} alt={item.name} width={WIDTH} />
</li>
);
})}
</ul>
</div>
);
};
export default React.memo(DragAndDropPage);
- Содержимое файла стиля следующее:
/** index.module.scss */
.wrapper {
overflow: hidden;
margin: 100px auto;
padding: 10px 0;
background: #fff;
border-radius: 5px;
}
.list {
list-style: none;
padding: 0;
margin: 0;
font-size: 0;
position: relative;
}
.item {
position: absolute;
display: flex;
align-items: center;
transition: all 0.2s ease-in-out;
}
Эффект следующий:
- Как видно из приведенного выше кода, хотя отображаемый
li
элемент выглядит какlist
Отображается исходный порядок, но фактический порядок рендеринга определяется отсортированным списком.sortedList
решил.
Причина, по которой он ведет себя какlist
Первоначальный порядок показанsortedList.map
время по текущемуitem
существуетlist
индекс массиваindex
Рассчитать его CSSleft
иtop
Атрибуты.
Перетащите реализацию
Перетащите, чтобы добавить специальные стили
- Во-первых, для самого внешнего контейнера
wrapper
связыватьref
атрибут, который удобно потом получить в методе событияDOM
узел, и поместите каждыйli
элементальdraggable
Значение свойства установлено вtrue/false
Сделайте его перетаскиваемым элементом при привязкеondragstart
событие:
-
handleDragStart
Метод в основном состоит в том, чтобы сохранить данные текущего перетаскиваемого элемента и добавить к нему специальные стили (установить прозрачность элемента и увеличить элемент) Соответствующий код выглядит следующим образом:
- На данный момент эффект следующий:
Перетащите конец, чтобы удалить специальные стили
- Как видите, когда вы начинаете перетаскивать элемент, к элементу применяется особый стиль, но элемент не восстанавливает исходный стиль после отпускания мыши. В это время вам нужно удалить специальный стиль, добавленный ранее после завершения перетаскивания.
- Чтобы уменьшить потребление памяти, мы помещаем перетаскиваемый элемент
ondragend
Событие делегируется самому внешнему контейнеру (делегирование события), и соответствующий код выглядит следующим образом:
- На данный момент эффект следующий:
перетащите, чтобы отсортировать
- Далее мы реализуем самую важную функцию перетаскивания, в основном для связывания элементов.
ondragover
событие. - По умолчанию данные/элементы не могут быть помещены в другие элементы. Если мы хотим добиться этого, нам нужно запретить обработку элементов по умолчанию. Мы можем позвонить
event.preventDefault()
метод для реализации события ondragover. - когда
ondragover
Когда событие срабатывает, вам нужно получить текущую позицию мыши (event.clientX
,event.clientY
), рассчитать, к какому элементу перетаскивается текущая мышь. Оценивая положение текущего перетаскиваемого элемента и других элементов, осуществляется обмен и сортировка элементов. Ключ в том, чтобы реализоватьupdateList
метод, соответствующий код выглядит следующим образом:
/** 将某元素插入到数组中的某位置 */
export function insertBefore<T>(list: T[], from: T, to?: T): T[] {
const copy = [...list];
const fromIndex = copy.indexOf(from);
if (from === to) {
return copy;
}
copy.splice(fromIndex, 1);
const newToIndex = to ? copy.indexOf(to) : -1;
if (to && newToIndex >= 0) {
copy.splice(newToIndex, 0, from);
} else {
// 没有 To 或 To 不在序列里,将元素移动到末尾
copy.push(from);
}
return copy;
}
/** 判断是否数组相等 */
export function isEqualBy<T>(a: T[], b: T[], key: keyof T) {
const aList = a.map((item) => item[key]);
const bList = b.map((item) => item[key]);
let flag = true;
aList.forEach((i, idx) => {
if (i !== bList[idx]) {
flag = false
}
})
return flag;
}
const DragAndDropPage: React.FC = () => {
const [list, setList] = useState(showList);
const dragItemRef = useRef<ListItem>();
const dropAreaRef = useRef<HTMLDivElement>(null);
...
const updateList = useCallback(
(clientX: number, clientY: number) => {
const dropRect = dropAreaRef.current?.getBoundingClientRect();
if (dropRect) {
const offsetX = clientX - dropRect.left;
const offsetY = clientY - dropRect.top;
const dragItem = dragItemRef.current;
// 超出拖动区域
if (
!dragItem ||
offsetX < 0 ||
offsetX > dropRect.width ||
offsetY < 0 ||
offsetY > dropRect.height
) {
return;
}
const col = Math.floor(offsetX / WIDTH);
const row = Math.floor(offsetY / HEIGHT);
let currentIndex = row * COLUMN + col;
const fromIndex = list.indexOf(dragItem);
if (fromIndex < currentIndex) {
// 从前往后移动
currentIndex++;
}
const currentItem = list[currentIndex];
const ordered = insertBefore(list, dragItem, currentItem);
if (isEqualBy(ordered, list, 'id')) {
return;
}
setList(ordered);
}
},
[list]
);
const handleDragOver = useCallback(
(e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
updateList(e.clientX, e.clientY);
},
[updateList]
);
return (
<div
className={styles.wrapper}
ref={dropAreaRef}
style={{ width: COLUMN * (WIDTH + IMAGE_PADDING) + IMAGE_PADDING }}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
>
<ul className={styles.list} style={{ height: listHeight }}>
{sortedList.map((item) => {
const index = list.findIndex((i) => i === item);
const row = Math.floor(index / COLUMN);
const col = index % COLUMN;
return (
<li
draggable
key={item.id}
className={styles.item}
style={{
height: HEIGHT,
left: col * (WIDTH + IMAGE_PADDING),
top: row * HEIGHT,
padding: `0 ${IMAGE_PADDING}px`,
}}
data-id={item.id}
onDragStart={(e) => handleDragStart(e, item)}
>
<img src={item.image} alt={item.name} width={WIDTH} />
</li>
);
})}
</ul>
</div>
);
};
export default React.memo(DragAndDropPage);
- Эффект следующий:
полный код
-
index.tsx
Содержимое файла:
- Содержимое файла стиля:
Резюме 👀
- В этой статье описывается, как
React
Реализовать простую сортировку изображений в проекте перетаскиванием. В основном за счет установки элементов, которые нужно перетаскиватьdraggable
свойства и слушать связанные события, добавлять или удалять стили, перетаскивать и сортировать и т. д.
Если есть какие-либо упущения или ошибки в вышеуказанном содержании, пожалуйста, оставьте сообщение ✍️Укажите и продвигайтесь вперед вместе💪💪💪
Если вы считаете, что эта статья полезна для вас, 🏀🏀 оставьте свой драгоценный 👍