页面有几个元素按顺序纵向排列,希望能够拖拽这些元素进行排列。
1.添加拖拽属性
<div class="tables">
<div draggable="true" class="item">1</div>
<div draggable="true" class="item">2</div>
<div draggable="true" class="item">3</div>
<div draggable="true" class="item">4</div>
<div draggable="true" class="item">5</div>
<div draggable="true" class="item">6</div>
</div>
这时候元素可以拖拽了,即跟着鼠标光标移动,但该元素的原来位置没有变。先处理样式,可以给元素添加一个新的 样式moving ,让其原来的位置看起来不一样。在元素被拖拽的时候,添加 moving类。
.item {
width: 400px;
height: 45px;
padding-left: 10px;
line-height: 45px;
background-color: pink;
border-radius: 5px;
margin-bottom: 20px;
cursor: pointer;
&.moving { //被拖拽时的样式
background: transparent;
color: transparent;
border: 1px dashed #ccc;
}
}
这时候使用事件代理监听拖拽事件。
const onTrag = () => {
const list = document.querySelector(".tables");
// 事件委托 开始拖拽时
list.ondragstart = (e) => {
e.target.classList.add("moving"); //改变元素样式,但此时拖拽的元素 和 原来的位置 的样式都变了
};
};
因为是刚拖拽的时候就添加样式,所以两个地方的样式都给改了。这时可放到一个异步环境里,拖拽开始的时候,跟随鼠标的样式不变。
list.ondragstart = (e) => {
setTimeout(() => {
e.target.classList.add("moving");
}, 0);
};
2. 排序
拖拽元素后,需要放到某个位置,所以需要监听鼠标进入了哪个元素。这里要排除一些特殊情况,进入父元素和进入本身时,不做任何处理。
const onTrag = () => {
const list = document.querySelector(".tables");
let sourceNode; //闭包 记录拖拽的元素
// 事件委托 开始拖拽时
list.ondragstart = (e) => {
setTimeout(() => {
e.target.classList.add("moving");
}, 0);
sourceNode = e.target;
};
// 拖拽进入
list.ondragenter = (e) => {
if (e.target === list || e.target === sourceNode) return;//不做处理
};
};
拖动元素的时候,有两个方向,向上和向下,那目标位置的元素就要做出相反的移动。现在可以通过获取 被拖动元素 和 进入位置元素 的下标来判断移动方向。
// 拖拽进入
list.ondragenter = (e) => {
if (e.target === list || e.target === sourceNode) return;
const children = [...list.children];
const sourceNodeIndex = children.indexOf(sourceNode);
const targetNodeIndex = children.indexOf(e.target);
if (sourceNodeIndex < targetNodeIndex) {
list.insertBefore(sourceNode, e.target.nextElementSibling); //加入到 目标元素 的下一个元素之前
} else {
list.insertBefore(sourceNode, e.target);//加入到 目标元素之前
}
};
这时候,已经是可以拖动元素,且目标元素会移动到被拖拽元素的位置了,比如拖拽元素1到元素2,那元素2就跑到元素1原来的位置了。此时也发现了,当松开的时候,元素1跑回去了,并没有进入元素2原来的位置。
其实,在默认情况下,所有的元素都都不允许别的东西拖动到自身,这是浏览器的默认行为。所以拖拽结束之后,它就是从哪来回哪去。那么解决这个问题的话,只需要阻止浏览器的默认行为。
//拖拽结束
list.ondragover = (e) => {
e.preventDefault();
};
最后,把目标位置元素的样式改回去就可以了。
//拖拽结束后
list.ondragend = (e) => {
e.target.classList.remove("moving");
};
完整代码:
const onTrag = () => {
const list = document.querySelector(".tables");
let sourceNode; //闭包 记录拖拽的元素
// 事件委托 开始拖拽时
list.ondragstart = (e) => {
setTimeout(() => {
e.target.classList.add("moving");
}, 0);
e.dataTransfer.effectAllowed = "move"; //解决拖拽过程中 鼠标光标变成一个 ➕ 的问题
sourceNode = e.target;
};
// 拖拽进入
list.ondragenter = (e) => {
e.preventDefault();
if (e.target === list || e.target === sourceNode) return;
const children = [...list.children];
const sourceNodeIndex = children.indexOf(sourceNode);
const targetNodeIndex = children.indexOf(e.target);
if (sourceNodeIndex < targetNodeIndex) {
list.insertBefore(sourceNode, e.target.nextElementSibling); //加入到 目标元素 的下一个元素之前
} else {
list.insertBefore(sourceNode, e.target); //加入到 目标元素之前
}
};
//拖拽结束
list.ondragover = (e) => {
e.preventDefault();
};
//拖拽结束后
list.ondragend = (e) => {
e.target.classList.remove("moving");
};
};