原生手动排序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<style>
.content-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.list-item {
cursor: pointer;
width: 50%;
border: 1px solid red;
background-color: aqua;
}
.moving {
background-color: unset;
border: 1px solid;
border-style: dashed;
}
</style>
<body>
<div class="content-list">
<div draggable="true" class="list-item">1</div>
<div draggable="true" class="list-item">2</div>
<div draggable="true" class="list-item">3</div>
<div draggable="true" class="list-item">4</div>
<div draggable="true" class="list-item">5</div>
</div>
</body>
<script>
let dom = document.querySelector(".content-list");
let currentDom;
dom.ondragstart = (e) => {
setTimeout(() => {
e.target.classList.add("moving");
}, 0);
currentDom = e.target;
};
dom.ondragover = (e) => {
e.preventDefault();
};
dom.ondragenter = (e) => {
e.preventDefault();
if (e.target !== dom && e.target !== currentDom) {
console.log(e.target);
}
const children = Array.from(dom.children);
const currentIndex = children.findIndex((item) => item === currentDom);
const dropIndex = children.findIndex((item) => item === e.target);
if (currentIndex > dropIndex) {
dom.insertBefore(currentDom, e.target);
} else {
dom.insertBefore(currentDom, e.target.nextElementSibling);
}
console.log(children, currentIndex);
};
dom.ondragend = (e) => {
setTimeout(() => {
e.target.classList.remove("moving");
}, 0);
};
</script>
</html>
拖放事件
- dragstart: 当用户开始拖动一个元素或选中的文本时触发。
- dragover: 当某被拖动的元素或选中的文本在另一个元素上方时触发。
- dragenter: 当某被拖动的元素或选中的文本进入有效的放置目标时触发。
- dragleave: 当某被拖动的元素或选中的文本离开有效的放置目标时触发。
- drag: 当元素或选中的文本被拖动时触发。
- drop: 当被拖动的元素或选中的文本在放置目标上被释放时触发。
- dragend: 当拖动操作结束时(释放鼠标按钮或按下 ESC 键)触发。
实现思路和代码解读
-
设置元素可拖动:为了使元素可拖动,你需要设置 draggable="true"属性。在你的代码中,每个.list-item 都设置了这个属性。
-
处理 dragstart 事件:当拖动开始时,一个 dragstart 事件被触发。在你的事件处理函数里,你使用 setTimeout 设置了一个微任务,延迟将拖动元素的类名设置为.moving,从而使其变为虚线框(类似占位符的效果)。同时,你保存了当前拖动的元素,以便之后使用。
-
处理 dragover 事件:dragover 事件在元素被拖动到另一个元素上方时触发。为了让元素能够放置,你需要阻止默认行为(默认情况下,数据/元素不能被放置到其他元素上)。
-
处理 dragenter 事件:dragenter 事件在拖动的元素进入放置目标时触发。在事件处理函数中,你检查了进入的目标,如果它既不是容器本身也不是当前拖动的元素,你会计算当前拖动元素和进入元素的索引,然后根据索引将当前拖动元素放置在适当的位置(前面或后面)。
-
处理 dragend 事件:dragend 事件在拖动结束时触发,无论操作成功与否。在事件处理函数中,你移除了.moving 类,恢复元素的原始样式。
关键点
-
e.preventDefault(): 在 dragover 和 dragenter 事件中使用,是为了允许将元素放置到其他元素上(改变默认不允许放置的行为)。
-
setTimeout(..., 0): 通过将操作推迟到调用栈清空之后,避免了在拖动过程中对元素样式的立即更改,这可以防止出现不必要的渲染问题。
-
insertBefore: 这个 DOM 操作方法用来在指定的子节点前插入一个新的子节点,如果参照节点为 null,则插入到子节点列表的末尾。
-
整体上,你的代码利用了 HTML5 拖放 API 执行元素的垂直排序。用户通过拖放操作可以重新安排.list-item 元素的顺序,而代码则负责处理所有必要的事件来使这些操作可能。