列表拖拽转换的例子
今天看到有些表格中能够通过拖动来换行,手动排序的功能,想来自己实现一下,类似效果如下
![](https://img.haomeiwen.com/i20528621/95b9d73a21b1dcdd.gif)
主要解决问题的办法
-
clientX和clientY
-
动画元素animition
解决思路
-
委托外部元素监听内容元素的(dragStart阶段,定位被拖动的元素)
-
委托外部元素监听元素已经被拉到哪个元素的位置 (使用dragover确定,停留在哪个元素的位置上)
-
为元素添加动画效果
-
利用node.appendChild相同元素实现元素转换,参考不使用append的remove使节点快速转移的方法
从一个简单的例子开始
![](https://img.haomeiwen.com/i20528621/cc5ef1d63279cdf6.gif)
HTML
<!---html部分--->
<ul id="container">
<li draggable="true">1</li>
<li draggable="true">2</li>
<li draggable="true">3</li>
<li draggable="true">4</li>
<li draggable="true">5</li>
<li draggable="true">6</li>
<li draggable="true">7</li>
<li draggable="true">8</li>
<li draggable="true">9</li>
<li draggable="true">10</li>
</ul>
<button onclick="resetAll()">Reset</button>
- 配置draggable=true,使元素变为滑动的
CSS
ul {
list-style: none;
}
li {
padding: 30px 15px;
background-color: cornflowerblue;
color: #fff;
border: 1px solid #333;
border-radius: 10px;
width: 30px;
text-align: center;
margin: 10px 0;
transition: transform 2s ease;
}
JS逻辑
const con = document.getElementById("container");
let list = [];
// 主要为了动画滑动的时候,用于控制滑动距离的
const init = () => {
lists = Array.prototype.slice
.call(document.querySelectorAll("li"))
.map((element, index) => {
element.dataset.index = index;
return {
index,
offsetX: element.offsetLeft + con.offsetLeft,
offsetY: element.offsetTop + con.offsetTop
};
});
};
init();
let startX = 0,
startY = 0;
// 重置按钮的回调
let resetAll = () => {
const list = Array.prototype.slice.call(
document.querySelectorAll("li")
);
list.forEach(item => {
item.style.transform = "translate(0, 0)";
});
};
// 用于控制滑动到鼠标移动位置的函数
const changePosition = (node, relativeX = 0, relativeY = 0) =>
node &&
(node.style.transform = `translate(${relativeX}px, ${relativeY}px)`);
// ondragstart中的event获取拖动元素的位置
con.ondragstart = function(event) {
const target = event.target;
startX = event.clientX;
startY = event.clientY;
};
// ondragend 拖动元素目前到达的位置
// 通过event.clientX和event.clientY可以获取鼠标松掉的位置
con.ondragend = event => {
const node = event.target;
const init = lists.find(item => item.index === +node.dataset.index);
changePosition(
node,
event.clientX - init.offsetX,
event.clientY - init.offsetY
);
};
关键点
-
使用dragstart获取元素的初始移动的位置
-
使用dragend获取元素最终拖动到鼠标落点的位置(用于后续动画移动)
-
这里注意的是clientX和clientY是元素相对视口中的位置,所以需要在元素原有位置的基础上进行适当的长度补偿即可
回到之前的例子
HTML
<ul class="container">
<li class="item" draggable="true">1</li>
<li class="item" draggable="true">2</li>
<li class="item" draggable="true">3</li>
<li class="item" draggable="true">4</li>
<li class="item" draggable="true">5</li>
<li class="item" draggable="true">6</li>
<li class="item" draggable="true">7</li>
<li class="item" draggable="true">8</li>
<li class="item" draggable="true">9</li>
<li class="item" draggable="true">10</li>
</ul>
CSS
ul {
list-style: none;
}
.item {
padding: 20px;
width: 400px;
background-color: cyan;
color: red;
border: 2px solid #333;
margin: 20px 0;
transition: transform 1s ease;
text-align: center;
}
JS逻辑
-
在拖动时保存需要移动元素的对象,并且保存该元素的下一个元素(之后通过insertBefore来实现元素的交换)
-
使用dropover来更新需要与之发生交换的元素,记录下最终的交换元素
-
在dropend中对双方元素进行交换,先通过动画实现交换特效,加一个定时器,实现元素的交换(等到动画结束实现真正dom的交换)
const con = document.querySelector(".container");
const dragObj = { nextObj: null, target: null };
const exchangeObj = { nextObj: null, target: null };
let timer;
const changePosition = (node, relativeX = 0, relativeY = 0) =>
node &&
(node.style.transform = `translate(${relativeX}px, ${relativeY}px)`);
function inserElem(exchange, target) {
if (target.target === exchange.target) {
return;
} else {
// nextObj没有说明与最后一个元素做交换
if (exchange.nextObj === null) {
con.appendChild(target.target);
} else {
// 将现在这个元素插入到需要交换的前一个元素之前
// 这里对于同元素的insertBefore会直接执行在原来节点内元素的删除和新节点内元素的append不需要手动操作
con.insertBefore(target.target, exchange.nextObj);
}
}
}
// 记录目前正在拖动的元素
con.ondragstart = event => {
dragObj.target = event.target;
dragObj.nextObj = event.target.nextElementSibling;
};
con.ondragend = event => {
if (exchangeObj.target && dragObj.target) {
// 先执行动画效果
changePosition(
dragObj.target,
0,
exchangeObj.target.offsetTop - dragObj.target.offsetTop
);
changePosition(
exchangeObj.target,
0,
dragObj.target.offsetTop - exchangeObj.target.offsetTop
);
if (timer) {
clearTimeout(timer);
}
// 动画结束后 定时器执行真正的dom交换
timer = setTimeout(() => {
inserElem(dragObj, exchangeObj);
inserElem(exchangeObj, dragObj);
changePosition(dragObj.target, 0, 0);
changePosition(exchangeObj.target, 0, 0);
}, 1000);
}
};
// 记录下目前划过的需要交换的元素
con.ondragover = event => {
exchangeObj.target = event.target;
exchangeObj.nextObj = event.target.nextElementSibling;
};
最近,疫情没办法回学校,做了一个博客自己玩玩,作为一个React菜鸡练手项目,有兴趣的可以看看my-koa-react-blog
网友评论