前端的拖动排序也是一直很想实践的对象之一。
排险者露出那毫无特点的微笑说:“这很难理解吗?当生命意识到宇宙奥秘的存在时,距它最终解开这个奥秘只有一步之遥了。”看到人们仍不明白,他接着说,“比如地球生命,用了四十多亿年时间才第一次意识到宇宙奥秘的存在,但那一时刻距你们建成爱因斯坦赤道只有不到四十万年时间,而这一进程最关键的加速期只有不到五百年时间。如果说那个原始人对字宙的几分钟凝视是看到了一颗宝石,其后你们所谓的整个人类文明,不过是弯腰去拾它罢了。
复习D&D
First Thing First,最基础的事情就是复习一下D&D的API
。主要是要知道拖动源和拖动目标各个事件
拖动源事件有
ondragstart
ondrag
ondragend
拖动目标事件有
ondragenter
ondragover
ondragleave
ondrop
有一点要注意,在可拖动元素上要设置draggable = "true"
,我一开始按照组件里的快捷方式就仅放置了draggable
,结果连拖动的苗头都看不到。
基本DOM
<ul id="drag" class="drag">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
</ul>
用JavaScript
给这些<li>
元素加上draggable
属性。
const lis = document.querySelectorAll(".drag li");
for (let i = 0; i < lis.length; i++) {
lis[i].setAttribute("draggable", true);
}
现在各<li>
元素都是可拖动的了,但没有任何其他的效果。
加上样式美化元素
ul {
list-style-type: disc;
margin-block-start: 0;
margin-block-end: 0;
margin-inline-start: 0;
margin-inline-end: 0;
padding-inline-start: 0;
}
li {
height: 50px;
line-height: 50px;
border: 1px solid #aaaaaa;
width: 200px;
padding: 0 10px;
}
目前的效果如下
第一步普通排序
普通排序的目标就是能在拖动的时候实现最基本的排序功能,交换元素的位置次序。
注意,这里最合理的方式是使用事件代理,在<ul>
上监听<li>
冒泡来的事件,根据event.target
判断事件源是哪个元素,这里我偷懒了,在每一个<li>
元素上增加事件监听。
首先要把拖动源元素缓存下来。
let draggingElement;
for (let i = 0; i < lis.length; i++) {
lis[i].setAttribute("draggable", true);
lis[i].addEventListener("dragstart", (event) => {
draggingElement = event.target;
});
}
接下来就是监听拖动后的变化了,思路是,当拖动某个节点进入另一个节点之后,交换两者的次序。
因为只需要第一次进入另一个节点后交换次序,所以只要监听dragenter
事件即可。
lis[i].addEventListener("dragenter", (event) => {
//...
});
此时源节点draggingElement
和目标节点event.target
都获得了,要做的就仅是交换两者节点的位置。要交换两者的位置,就要用到insertBefore
的API
。
但注意insertBefore
仅是一个单向的动作,没有insertAfter
可供调用,所以在交换之前要判断一下拖动的源节点和目标节点哪个在前哪个在后。于是交换位置的代码如下
const order = Array.from(node.parentElement.children).indexOf(node);
//从大的序号移入到小的序号
if (draggingElementOrder > order) {
node.parentElement.insertBefore(draggingElement, node);
}
//从小的序号移入到大的序号
else {
//节点不是最后一个
if (node.nextElementSibling) {
node.parentElement.insertBefore(draggingElement, nod