实现拖拽来调整表格顺序
之前实习的时候,有一个需求就是:在表格中需要可以实现拖拽调整顺序,当时是依靠的vuedraggable
这个包实现的。那现在呢我就想看看原生怎么实现拖拽调整顺序。
先定义一个静态的结构:(这就是我们要拖拽的结构)
<ul>
<li data-index="1">一</li>
<li data-index="2">二</li>
<li data-index="3">三</li>
<li data-index="4">四</li>
<li data-index="5">五</li>
<li data-index="6">六</li>
<li data-index="7">七</li>
<li data-index="8">八</li>
</ul>
draggable
默认html的所有标签基本都不可以拖拽,除了图片、链接和已经选中的文字。如果我们想除了这些之外的标签也可以拖拽,就得用draggable
这个标签属性,将其设置为true
。
<ul>
<li data-index="1" draggable="true">一</li>
<li data-index="2" draggable="true">二</li>
<li data-index="3" draggable="true">三</li>
<li data-index="4" draggable="true">四</li>
<li data-index="5" draggable="true">五</li>
<li data-index="6" draggable="true">六</li>
<li data-index="7" draggable="true">七</li>
<li data-index="8" draggable="true">八</li>
</ul>
注意,一旦某个元素节点的
draggable
属性设为true
,就无法再用鼠标选中该节点内部的文字或子节点了。
拖拽事件
我们设置了draggable
仅仅是可以将其拖拽了,但是松开鼠标这个元素还在是原位置的。这是因为浏览器页面默认是阻止元素在其上面拖拽的,如果我们要让其允许在上面拖拽,是需要去进行设置的。但是在设置之前,我们得先了解一下这个拖拽的相关事件。
drag
:拖拉过程中,在被拖拉的节点上持续触发(相隔几百毫秒)。dragstart
:用户开始拖拉时,在被拖拉的节点上触发,该事件的target
属性是被拖拉的节点。通常应该在这个事件的监听函数中,指定拖拉的数据。dragend
:拖拉结束时(释放鼠标键或按下 ESC 键)在被拖拉的节点上触发,该事件的target
属性是被拖拉的节点。它与dragstart
事件,在同一个节点上触发。不管拖拉是否跨窗口,或者中途被取消,dragend
事件总是会触发的。
(以上是被拖拽节点的事件,就是我们要移动的元素的事件,以下是拖拽节点要放置的目标节点的事件)
dragenter
:拖拉进入当前节点时,在当前节点上触发一次,该事件的target
属性是当前节点。通常应该在这个事件的监听函数中,指定是否允许在当前节点放下(drop)拖拉的数据。如果当前节点没有该事件的监听函数,或者监听函数不执行任何操作,就意味着不允许在当前节点放下数据。在视觉上显示拖拉进入当前节点,也是在这个事件的监听函数中设置。dragover
:拖拉到当前节点上方时,在当前节点上持续触发(相隔几百毫秒),该事件的target
属性是当前节点。该事件与dragenter
事件的区别是,dragenter
事件在进入该节点时触发,然后只要没有离开这个节点,dragover
事件会持续触发。dragleave
:拖拉操作离开当前节点范围时,在当前节点上触发,该事件的target
属性是当前节点。如果要在视觉上显示拖拉离开操作当前节点,就在这个事件的监听函数中设置。drop
:被拖拉的节点或选中的文本,释放到目标节点时,在目标节点上触发。注意,如果当前节点不允许drop
,即使在该节点上方松开鼠标键,也不会触发该事件。如果用户按下 ESC 键,取消这个操作,也不会触发该事件。该事件的监听函数负责取出拖拉数据,并进行相关处理。
此外我们要注意一个点是,
dragover
、dragenter
和dragleave
是会进行冒泡的,我们得阻止其冒泡,不然整个页面都会触发这几个事件了(当时做的时候坑了我半天)
然后我们就要给每一个li
都绑定dragstart
事件,同时将当前移动的元素保存下来。
let tempLi;
let lis = ul.querySelectorAll('li');
lis.forEach((li) => {
li.addEventListener('dragstart', function (e) {
tempLi = e.target; // // 保存被拖拉节点
e.target.style.opacity = 0.5; // 被拖拉节点的背景色变透明
})
})
然后我们将ul添加事件dragover
,因为dragover
的默认行为会组织我们在其上面拖拽。同时保存目前所移动的位置和阻止其冒泡(这样当目标元素拖拽离开目标距离时不会进行拖拽)。
ul.addEventListener('dragover', function (e) {
e.preventDefault();// 防止拖拉效果被重置,允许被拖拉的节点放入目标节点
e.stopPropagation(); //组织冒泡
aim = e.target;
move(tempLi, aim); // 改变原列表数据
})
我们得到这个移动的位置后,就将我们要拖拽的元素移动到这里来:
let ul = document.querySelector('ul');
function move(tempLi, aim) {
if (aim == ul) {
aim.appendChild(tempLi);
} else {
aim.parentNode.insertBefore(tempLi, aim)
}
}
因为e.target
指向的是实际触发事件的目标,所以它指向的有两类:一类是li,一类是ul,如果是ul,我们将将其添加到最后面,如果是li,我们就让其添加到这个li的前面。
完整代码:
<head>
<style>
* {
padding: 0;
margin: 0;
}
body {
width: 100%;
height: 100vh;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
}
ul {
list-style: none;
width: 300px;
height: 500px;
background-color: #fff;
padding: 15px 10px;
border: 1px solid #ccc;
}
ul li {
width: 100%;
padding: 10px 0;
border-bottom: 1px solid #ccc;
color: rgb(114, 114, 114);
font-size: 16px;
}
ul li:last-child {
border: none;
}
ul li:nth-child(odd) {
background-color: aliceblue;
}
</style>
</head>
<body>
<ul>
<li data-index="1" draggable="true">一</li>
<li data-index="2" draggable="true">二</li>
<li data-index="3" draggable="true">三</li>
<li data-index="4" draggable="true">四</li>
<li data-index="5" draggable="true">五</li>
<li data-index="6" draggable="true">六</li>
<li data-index="7" draggable="true">七</li>
<li data-index="8" draggable="true">八</li>
</ul>
<script>
let tempLi, aim;
let ul = document.querySelector('ul');
let lis = ul.querySelectorAll('li');
lis.forEach((li) => {
li.addEventListener('dragstart', function (e) {
tempLi = e.target; // // 保存被拖拉节点
e.target.style.opacity = 0.5; // 被拖拉节点的背景色变透明
})
li.addEventListener('dragend', function (e) {
// 被拖拉节点的背景色恢复正常
e.target.style.opacity = '1';
})
})
ul.addEventListener('dragover', function (e) {
e.preventDefault();// 防止拖拉效果被重置,允许被拖拉的节点放入目标节点
e.stopPropagation(); //组织冒泡
aim = e.target;
move(tempLi, aim); // 改变原列表数据
})
function move(tempLi, aim) {
console.log(aim);
if (aim == ul) {
aim.appendChild(tempLi);
} else {
aim.parentNode.insertBefore(tempLi, aim)
}
}
</script>
</body>
实际效果:
此外,如果需要做一些高难度的东西的话,就可能需要拖拽的很多其他知识了,比如DataTransfer
用来读写需要传递的数据等等。下面我放一个我学习这块知识的链接(我觉得写得很好的~~)
转载自:https://juejin.cn/post/7137214100517912613