列表拖拽转换的例子
今天看到有些表格中能够通过拖动来换行,手动排序的功能,想来自己实现一下,相似效果如下
image
主要处理问题的办法
drag元素
clientX和clientY
动画元素animition
处理思路
委托外部元素监听内容元素的(dragStart阶段,定位被拖动的元素)
委托外部元素监听元素已经被拉到哪个元素的位置 (使用dragover确定,停留在哪个元素的位置上)
为元素增加动画效果
利用node.appendChild相同元素实现元素转换,参考不使用append的remove使节点快速转移的方法
从一个简单的例子开始
image
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