本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com
Javascript元素拖动
刚开始,咱们循序渐进,先来实现一个最简单的功能,让一个元素变成可拖动元素。
布局与样式:
<!DOCTYPE html><html><head> <title>元素拖动</title> <style> #drag { width: 100px; height: 100px; border: 1px solid #cbd5e0; display: flex; justify-content: center; align-items: center; cursor: move; user-select: none; position: absolute; } </style></head><body> <div id="drag">橙某人</div></body>要让一个元素可拖动,我们需要处理三个事件:
mousedown[2]:按下。
mousemove[3]:移动。
mouseup[4]:释放。
三者都是老演员了,相信每个前端人都识得啦😋,具体详情可以点链接上 MDN 查阅。
具体逻辑过程:
<script> document.addEventListener('DOMContentLoaded', () => { const drag = document.getElementById('drag'); // 添加鼠标按下事件 drag.addEventListener('mousedown', mouseDownHandler); // 记录鼠标坐标信息 let x = 0; let y = 0; // 鼠标按下事件 function mouseDownHandler(e) { // 记录鼠标初始位置 x = e.clientX; y = e.clientY; // 添加鼠标移动与释放事件 document.addEventListener('mousemove', mouseMoveHandler); document.addEventListener('mouseup', mouseUpHandler); }; // 鼠标移动事件 function mouseMoveHandler(e) { // 计算鼠标拖动的距离 const dx = e.clientX - x; const dy = e.clientY - y; // 将拖动距离赋值给目标元素 drag.style.top = `${drag.offsetTop + dy}px`; drag.style.left = `${drag.offsetLeft + dx}px`; // 不断记录鼠标上一个位置 x = e.clientX; y = e.clientY; }; // 鼠标释放事件 function mouseUpHandler() { // 重置相关变量 x = 0; y = 0; // 移除事件 document.removeEventListener('mousemove', mouseMoveHandler); document.removeEventListener('mouseup', mouseUpHandler); }; });</script>offsetTop[5]:元素到 offsetParent 顶部的距离。
offsetParent[6]:距离元素最近的一个具有定位的祖宗元素(relative,absolute,fixed),若祖宗都不符合条件,offsetParent 为 body。
效果:
大致原理过程:计算鼠标拖动的横向与纵向距离,再结合元素本身的 offsetTop 与 offsetLeft 信息,就能实现元素的拖动。
没几行代码,应该不难哈,都写上详细的注释啦。
上面用
clientX/Y获取鼠标的位置信息,那用pageX/Y可以不呢❓
clientX:提供了鼠标指针相对于浏览器视口(即当前可见的页面部分)左上角的水平坐标。不论页面是否滚动,clientX的值都是相对于视口的。
pageX:提供了鼠标指针相对于整个页面左上角的水平坐标,包括了任何由于滚动而不可见的部分。当你滚动页面时,pageX的值会改变,因为它考虑了滚动的距离。简而言之,就是要不要考虑滚动条的问题,如果你想要获取鼠标指针相对于整个页面的位置,应该使用
pageX。如果你只关心鼠标指针在当前视口内的位置,那么clientX就足够了。网上找了个图,可以瞧瞧:
列表拖动
简单的整完,咱们开始上点强度💀,这次要做的功能如下:
04092.gif
看着可能稍微有点复杂,但实际还好啦,我们一步一步来完成。
一样,先把布局与样式整一下:
<!DOCTYPE html><html><head> <title>列表拖动</title> <style> .item { width: 200px; height: 60px; border: 1px solid #cbd5e0; display: flex; justify-content: center; align-items: center; cursor: move; user-select: none; margin: 10px 0; box-sizing: border-box; } </style></head><body> <div id="list"> <div class="item">第一个元素</div> <div class="item">第二个元素</div> <div class="item">第三个元素</div> <div class="item">第四个元素</div> <div class="item">第五个元素</div> </div></body>接下来,让每个元素变成可拖动元素。
<script> document.addEventListener('DOMContentLoaded', () => { const list = document.getElementById('list'); list.querySelectorAll('.item').forEach(item => { // 批量添加事件 item.addEventListener('mousedown', mouseDownHandler); }); // 记录鼠标在拖动元素上的位置信息 let x = 0; let y = 0; // 记录当前的拖动元素 let draggingElement; function mouseDownHandler(e) { // 记录拖动元素 draggingElement = e.target; // 计算鼠标在拖动元素上的位置信息 const rect = draggingElement.getBoundingClientRect(); x = e.clientX - rect.left; y = e.clientY - rect.top; document.addEventListener('mousemove', mouseMoveHandler); document.addEventListener('mouseup', mouseUpHandler); }; function mouseMoveHandler(e) { // 计算拖动元素的最新位置 const left = e.clientX - x; const top = e.clientY - y; // 将移动距离赋值给目标元素 draggingElement.style.position = 'absolute'; draggingElement.style.top = `${top}px`; draggingElement.style.left = `${left}px`; }; function mouseUpHandler() { // 布局恢复原样(列表布局肯定一直是一个样啦) draggingElement.style.removeProperty('top'); draggingElement.style.removeProperty('left'); draggingElement.style.removeProperty('position'); x = 0; y = 0; draggingElement = null; document.removeEventListener('mousemove', mouseMoveHandler); document.removeEventListener('mouseup', mouseUpHandler); } });</script>与前面讲的差不多,熟悉的配方。😗
稍微有一点区别是,将元素变成可拖动的逻辑与前面讲的不太一致了。😓
这里用上了 getBoundingClientRect[7] API,其作用是为了优化前面在 mouseMoveHandler 函数中,需要不断去记录鼠标上一个位置的繁琐过程。
大概二者的区别如下:
1️⃣ 拖动元素的位置 = 拖动元素原本位置 + 拖动距离
2️⃣ 拖动元素的位置 = 根据鼠标最新位置直接计算拖动元素的最新位置 = 鼠标最新位置 - 鼠标在拖动元素上的距离
鼠标在拖动元素上的距离:
image.png
效果如下:
04093.gif
现在每个元素都能拖动了,只是还没有加上交换的逻辑。
但,这看着是不是有点奇怪?当拖动一个元素,列表下面的元素就顶上来了,这与咱们一开始看到的效果不太一致吖❗
这是因为缺少了一个占位元素,当在拖动元素时,需要自动插入一个占位元素,保持列表布局不会变化,拖动交换元素时,也应该是占位元素与其他元素进行交换,拖动结束时,再将占位元素给删除,将位置让给拖动元素。
下面,我们来看看如何解决这个占位元素的问题。
<!DOCTYPE html><html><head> <style> ... /* 占位元素样式 */ .placeholder { box-sizing: border-box; background-color: #edf2f7; margin: 10px 0; border: 2px dashed #cbd5e0; } </style></head><script>document.addEventListener('DOMContentLoaded', () => { ... let draggingElement; // 站位元素 let placeholder; // 是否正在拖动中 let isDraggingStarted = false; function mouseMoveHandler(e) { const draggingRect = draggingElement.getBoundingClientRect(); // 仅在移动中初次创建一次 if (!isDraggingStarted) { isDraggingStarted = true; // 创建占位元素 placeholder = document.createElement('div'); // 给占位元素添加class placeholder.classList.add('placeholder'); // 插入占位元素 draggingElement.parentNode.insertBefore(placeholder, draggingElement.nextSibling); // 保持占位元素与拖动元素一样大小 placeholder.style.width = draggingRect.width + 'px'; placeholder.style.height = draggingRect.height + 'px'; } const left = e.clientX - x; ... }; function mouseUpHandler() { // 拖动结束清除占位元素 placeholder && placeholder.parentNode.removeChild(placeholder); isDraggingStarted = false; draggingElement.style.removeProperty('top'); ... };});</script>加了大概十行代码,都是比较基础的 DOM 操作。👀
瞧瞧效果:
04101.gif
有那味了。🤡
最后,咱们差一步了,就是根据拖动方向进行元素之间的交换。
看到 "拖动方向" 加粗没😯?这是关键点,我们要如何知道拖动元素是往上还是往下呢❓并且交换元素位置的时机如何把握呢❓
看如下图,假设了中间三个元素的中心点坐标分别如下图。
image.png
当我们拖动第三个元素,往上,第三个元素的中心点坐标会不断变化,可能会变成(10,19)、(10,18)、(10,17)、......。
当继续往上拖动,第三个元素中心点坐标变成(10,9)时,它的纵坐标比第二个元素中心点纵坐标小了,这个时候就需要交换位置❗
根据这个原理过程,咱们先来写一个判断拖动方向的函数,如下:
// 检测拖动元素是否向上拖动function isAbove(nodeA, nodeB) { const rectA = nodeA.getBoundingClientRect(); const rectB = nodeB.getBoundingClientRect(); // 计算中心点纵坐标 const centerPointA = rectA.top + rectA.height / 2; const centerPointB = rectB.top + rectB.height / 2; return centerPointA < centerPointB;};应该很好理解吧?🌟
交换元素的过程,咱们也可以单独写一个函数,如下:
// 交换两个相邻的元素位置function swap(nodeA, nodeB) { // 获取父节点,为后续插入提供一个支点 const parentA = nodeA.parentNode; // 获取参照节点 const siblingA = nodeA.nextSibling === nodeB ? nodeA : nodeA.nextSibling; // 将A节点移动到参照节点之前 nodeB.parentNode.insertBefore(nodeA, nodeB); // 将B节点移动到参照节点之前 parentA.insertBefore(nodeB, siblingA);};虽然只有短短四行代码,但这个时候就非常考验你的 Javascript 基础了。
(当然,这种东西你也可以直接问 GPT,捡现成的。👻)
最后,结合具体逻辑:
function mouseMoveHandler() { ... // 获取拖动时的上下元素 const prevEle = draggingElement.previousElementSibling; const nextEle = placeholder.nextElementSibling; // 向上移动(没上一个元素了,代表到边界了,不用处理) if (prevEle && isAbove(draggingElement, prevEle)) { // 占位元素要先与拖动元素交换位置❗ swap(placeholder, draggingElement); // 占位元素与上一个元素交换位置 swap(placeholder, prevEle); return; } // 向下移动 if (nextEle && !isAbove(draggingElement, nextEle)) { swap(nextEle, placeholder); swap(nextEle, draggingElement); }}我们仅需在 mouseMoveHandler 函数中添加交换逻辑即可。
这里要注意 "占位元素要先与拖动元素交换位置",可能你会有疑问?不是直接交换占位元素与上一个元素(或下一个元素)就行咩?😢
我们可以看看实际的 DOM 结构,第二个元素与占位元素中间还隔着拖动元素呢,注意我们是要交换两个相邻的元素,不是随便两个相隔遥远的元素哦。
image.png
好,到此完毕,列表拖动就完成啦。👻
表格拖动 - 列
接下来要做的是表格上的拖动,也是比较常见的功能了,话不多说,先看效果图:
04102.gif
做之前咱们先来分析一波,由于我们要拖动的是列,是竖着纵向排列的,而表格可是按照横向进行布局的❗
表格的布局结构:
<table id="table" class="table"> <thead> <tr> <th>序号</th> <th>日期</th> <th>姓名</th> <th>省份</th> <th>城市</th> <th>地址</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>2024-04-01</td> <td>小小红</td> <td>广东</td> <td>广州市</td> <td>广东省广州市xxxxxx</td> </tr> <tr> <td>2</td> <td>2024-04-02</td> <td>小小绿</td> <td>广东</td> <td>深圳市</td> <td>广东省深圳市xxxxxx</td> </tr> <tr> <td>3</td> <td>2024-04-03</td> <td>小小黄</td> <td>广东</td> <td>中山市</td> <td>广东省中山市xxxxx</td> </tr> <tr> <td>4</td> <td>2024-04-04</td> <td>小小白</td> <td>广东</td> <td>佛山市</td> <td>广东省佛山市xxxx</td> </tr> <tr> <td>5</td> <td>2024-04-05</td> <td>小小黑</td> <td>广东</td> <td>汕头市</td> <td>广东省汕头市xxxxxx</td> </tr> </tbody></table>image.png
这... 可不利于我们拖动处理呀❗我们要拖动的列必须是一个整体、是整个块,要是跨元素就麻烦了。
🌟这里咱们就要换个思路了,在要开始拖动时,动态创建一个纵向的列表,列表的每一子项就是表格的列,其实就是将表格转成我们上面已经讲过的列表拖动来进行操作;然后隐藏原表格,操作这个新列表,当拖动结束的时候,我们再通过列表的索引信息来交换表格的格子就行啦,是不是手拿把掐。😁(注意是拖动列表的项!!!)
那咱们先来看看如何动态创建出这个列表叭。😉
相关 HTML 结构就是上面那个表格布局了,没了。
相关 CSS 样式:
<!DOCTYPE html><html><head> <style> .table { border: 1px solid #ccc; border-collapse: collapse; } .table th, .table td { border: 1px solid #ccc; } .table th, .table td { padding: 10px; text-align: center; box-sizing: border-box; } .draggable { cursor: move; user-select: none; } .list { border-left: 1px solid #ccc; border-top: 1px solid #ccc; display: flex; } .list__table { border-collapse: collapse; border: none; } .list__table th, .list__table td { border: 1px solid #ccc; border-left: none; border-top: none; box-sizing: border-box; padding: 10px; text-align: center; color: red; /* 为了演示,加个红色 */ } .placeholder { background-color: #edf2f7; border: 2px dashed #cbd5e0; box-sizing: border-box; } .dragging { background: #fff; border-left: 1px solid #ccc; border-top: 1px solid #ccc; z-index: 999; } </style></head>上面列出了所有样式,对照着看看就行,样式不是重点,但是其中值得关注的是动态创建的列表,它的 border 是如何变成和表格的一样?因为稍微缺失一个格子的边框,可能就会造成列表和原表格不重合,容易露馅,这点你可以仔细琢磨一下。
<script>document.addEventListener('DOMContentLoaded', () => { const table = document.getElementById('table'); table.querySelectorAll('th').forEach(headerCell => { // 给可拖动元素添加一个样式类 headerCell.classList.add('draggable'); headerCell.addEventListener('mousedown', mouseDownHandler); }); let draggingElement; // 记录拖动列的索引 let draggingColumnIndex; let x = 0; let y = 0; let isDraggingStarted = false; function mouseDownHandler(e) { // 找到拖动列的索引 draggingColumnIndex = [].slice.call(table.querySelectorAll('th')).indexOf(e.target); x = e.clientX - e.target.offsetLeft; y = e.clientY - e.target.offsetTop; document.addEventListener('mousemove', mouseMoveHandler); document.addEventListener('mouseup', mouseUpHandler); }; function mouseMoveHandler(e) { if (!isDraggingStarted) { isDraggingStarted = true; // 动态创建列表 createList(); } } // 创建一个表格列表 function createList() { const rect = table.getBoundingClientRect(); list = document.createElement('div'); list.classList.add('list'); // 覆盖在表格上,如果是在局部,需要在共同的父元素上加上relative,小编这里父元素是body,就不用了。 list.style.position = 'absolute'; list.style.left = rect.left + 'px'; list.style.top = rect.top + 'px'; // 列表插入文档中 table.parentNode.insertBefore(list, table); // 隐藏原表格 table.style.visibility = 'hidden'; // 获取表格所有的格子 const originalCells = [].slice.call(table.querySelectorAll('tbody td')); // 获取表格的表头格子 const originalHeaderCells = [].slice.call(table.querySelectorAll('th')); const numColumns = originalHeaderCells.length; // 循环列 originalHeaderCells.forEach((headerCell, headerIndex) => { const { width } = window.getComputedStyle(headerCell); // 创建列表子项,也就是新的列 const item = document.createElement('div'); item.classList.add('draggable'); // 子项是一个只有单列的表格,这就是上面样式中提到的列表的border如何保持和表格的边框一样的技巧 const newTable = document.createElement('table'); newTable.setAttribute('class', 'list__table'); newTable.style.width = width; // 子项表格的表头 const th = headerCell.cloneNode(true); let newRow = document.createElement('tr'); newRow.appendChild(th); newTable.appendChild(newRow); // 子项表格的数据,在所有格子中找到属于这一列的格子 const cells = originalCells.filter((c, idx) => { // 代入几个格子索引算算就清楚啦😉 return (idx - headerIndex) % numColumns === 0; }); // 找到这列的格子后,给格子加上对应列的宽度,再把它们包装成一个行tr,再插入就可以了 cells.forEach(cell => { const newCell = cell.cloneNode(true); newCell.style.width = width + 'px'; newRow = document.createElement('tr'); newRow.appendChild(newCell); newTable.appendChild(newRow); }); // 把子项表格追加到新列中,再把新列追加到列表中,完事 item.appendChild(newTable); list.appendChild(item); }); } function mouseUpHandler() { document.removeEventListener('mousemove', mouseMoveHandler); document.removeEventListener('mouseup', mouseUpHandler); }});</script>很熟悉吧,代码中很多都是前面讲过的了。
重点只有 createList 函数,它的作用就是创建一个与表格一样的列表,外观是一致的,只是与表格不同的是,它的布局是纵向的,就这么简单,详细的可以瞧瞧代码过程。👻
image.png
现在列表有了,操作列表的拖动这块咱熟呀,直接整上。
仅需改动 mouseMoveHandler 函数:
function mouseMoveHandler(e) { if (!isDraggingStarted) { isDraggingStarted = true; createList(); // 通过索引获取拖动元素 draggingElement = [].slice.call(list.children)[draggingColumnIndex]; draggingElement.classList.add('dragging'); // 继续创建占位元素 placeholder = document.createElement('div'); placeholder.classList.add('placeholder'); draggingElement.parentNode.insertBefore(placeholder, draggingElement.nextSibling); // 因为是flex布局,不用设置高度也可以 placeholder.style.width = draggingElement.offsetWidth + 'px'; } // 和元素拖动的过程一样 draggingElement.style.position = 'absolute'; draggingElement.style.top = (draggingElement.offsetTop + e.clientY - y) + 'px'; draggingElement.style.left = (draggingElement.offsetLeft + e.clientX - x) + 'px'; x = e.clientX; y = e.clientY; // 交换元素,与列表拖动的一样 const prevEle = draggingElement.previousElementSibling; const nextEle = placeholder.nextElementSibling; if (prevEle && isOnLeft(draggingElement, prevEle)) { swap(placeholder, draggingElement); swap(placeholder, prevEle); return; } if (nextEle && isOnLeft(nextEle, draggingElement)) { // 元素换个位置而已,用!取反也是一样的 swap(nextEle, placeholder); swap(nextEle, draggingElement); }}function swap(nodeA, nodeB) { const parentA = nodeA.parentNode; const siblingA = nodeA.nextSibling === nodeB ? nodeA : nodeA.nextSibling; nodeB.parentNode.insertBefore(nodeA, nodeB); parentA.insertBefore(nodeB, siblingA);};function isOnLeft(nodeA, nodeB) { const rectA = nodeA.getBoundingClientRect(); const rectB = nodeB.getBoundingClientRect(); const centerPointA = rectA.left + rectA.width / 2; const centerPointB = rectB.left + rectB.width / 2; return centerPointA < centerPointB;};新增加的 swap 与 isOnLeft 函数在列表拖动的时候都讲过啦,这里就不多说了。
做到这里,你的表格(列表)应该是可以正常拖动了,只是拖动后的效果还能不真正同步到表格中而已,差最后一步,咱也给它加上、加上。😀
咱们仅需要改动 mouseUpHandler 函数,在拖动结束的时候将列表子项的索引信息同步回原表格上,然后把列表移除就可以了。
具体如下:
function mouseUpHandler() { // 移除占位元素 placeholder && placeholder.parentNode.removeChild(placeholder); // 恢复拖动元素样式 draggingElement.classList.remove('dragging'); draggingElement.style.removeProperty('top'); draggingElement.style.removeProperty('left'); draggingElement.style.removeProperty('position'); // 获取拖动元素最后的索引 const endColumnIndex = [].slice.call(list.children).indexOf(draggingElement); isDraggingStarted = false; // 移除创建的列表 list.parentNode.removeChild(list); // 将列表的信息同步到原表格中 table.querySelectorAll('tr').forEach(row => { // 获取每一行的格子 const cells = [].slice.call(row.querySelectorAll('th, td')); if (draggingColumnIndex > endColumnIndex) { // 往左拖动 // 将目标格子(cells[draggingColumnIndex])放到最新的位置上 cells[endColumnIndex].parentNode.insertBefore( cells[draggingColumnIndex], cells[endColumnIndex]); }else { // 往右拖动 cells[endColumnIndex].parentNode.insertBefore( cells[draggingColumnIndex], cells[endColumnIndex].nextSibling); } }); // 恢复原表格的展示 table.style.removeProperty('visibility'); document.removeEventListener('mousemove', mouseMoveHandler); document.removeEventListener('mouseup', mouseUpHandler);};应该不是很难哈,都写上了详细的注释。
好啦,就这么多,到此,咱们就完成了开头看到的表格列拖动的效果了。👻👻👻
表格拖动 - 行
既然讲了表格的列拖动了,那么行的拖动肯定也是不能落下啦。😁
不过现在我们有了前面的基础,这个不是洒洒水?有手就行?
HTML 结构不变。
CSS 样式略微调整一下:
....list { /* border-left: 1px solid #ccc; */ border-top: 1px solid #ccc; /* display: flex; */}.list__table th,.list__table td { border: 1px solid #ccc; /* border-left: none; */ border-top: none; box-sizing: border-box; padding: 10px; text-align: center; color: red;}...主要还是 JS 逻辑部分:
<script>document.addEventListener('DOMContentLoaded', () => { const table = document.getElementById('table'); table.querySelectorAll('tr').forEach((row, index) => { // 表格不能拖动,跳过 if (index === 0) return; // 第一列的第一个格子才能拖动 const firstCell = row.firstElementChild; firstCell.classList.add('draggable'); firstCell.addEventListener('mousedown', mouseDownHandler); }); // 记录拖动的行索引 let draggingColumnIndex; let x = 0; let y = 0; let isDraggingStarted = false; let list; let draggingElement; let placeholder; function mouseDownHandler(e) { const originalRow = e.target.parentNode; draggingRowIndex = [].slice.call(table.querySelectorAll('tr')).indexOf(originalRow); x = e.clientX; y = e.clientY; document.addEventListener('mousemove', mouseMoveHandler); document.addEventListener('mouseup', mouseUpHandler); }; function mouseMoveHandler(e) { if (!isDraggingStarted) { isDraggingStarted = true; createList(); draggingElement = [].slice.call(list.children)[draggingRowIndex]; draggingElement.classList.add('dragging'); placeholder = document.createElement('div'); placeholder.classList.add('placeholder'); draggingElement.parentNode.insertBefore(placeholder, draggingElement.nextSibling); placeholder.style.height = draggingElement.offsetHeight + 'px'; } draggingElement.style.position = 'absolute'; draggingElement.style.top = (draggingElement.offsetTop + e.clientY - y) + 'px'; draggingElement.style.left = (draggingElement.offsetLeft + e.clientX - x) + 'px'; x = e.clientX; y = e.clientY; const prevEle = draggingElement.previousElementSibling; const nextEle = placeholder.nextElementSibling; if (prevEle && prevEle.previousElementSibling && isAbove(draggingElement, prevEle)) { swap(placeholder, draggingElement); swap(placeholder, prevEle); return; } if (nextEle && isAbove(nextEle, draggingElement)) { swap(nextEle, placeholder); swap(nextEle, draggingElement); } }; function createList() { const rect = table.getBoundingClientRect(); const width = window.getComputedStyle(table).width; list = document.createElement('div'); list.classList.add('list'); list.style.position = 'absolute'; list.style.left = rect.left + 'px'; list.style.top = rect.top + 'px'; table.parentNode.insertBefore(list, table); table.style.visibility = 'hidden'; // 循环行 table.querySelectorAll('tr').forEach(row => { const item = document.createElement('div'); item.classList.add('draggable'); // 子项是一个只有一行的表格,这就是上面样式中提到的列表的border如何保持和表格的边框一样的技巧 const newTable = document.createElement('table'); newTable.setAttribute('class', 'list__table'); newTable.style.width = width; const newRow = document.createElement('tr'); const cells = [].slice.call(row.children); cells.forEach(cell => { const newCell = cell.cloneNode(true); // 每个格子还是原来格子的宽度 newCell.style.width = window.getComputedStyle(cell).width; newRow.appendChild(newCell); }); newTable.appendChild(newRow); item.appendChild(newTable); list.appendChild(item); }); }; function swap(nodeA, nodeB) { // ... 一样的,不写了 }; function isAbove(nodeA, nodeB) { const rectA = nodeA.getBoundingClientRect(); const rectB = nodeB.getBoundingClientRect(); const centerPointA = rectA.top + rectA.height / 2; const centerPointB = rectB.top + rectB.height / 2; return centerPointA < centerPointB; }; function mouseUpHandler() { placeholder && placeholder.parentNode.removeChild(placeholder); draggingElement.classList.remove('dragging'); draggingElement.style.removeProperty('top'); draggingElement.style.removeProperty('left'); draggingElement.style.removeProperty('position'); const endRowIndex = [].slice.call(list.children).indexOf(draggingElement); isDraggingStarted = false; list.parentNode.removeChild(list); let rows = [].slice.call(table.querySelectorAll('tr')); // 行的交换就简单了,直接整行换就行了 if (draggingRowIndex > endRowIndex) { rows[endRowIndex].parentNode.insertBefore( rows[draggingRowIndex], rows[endRowIndex] ); }else { rows[endRowIndex].parentNode.insertBefore( rows[draggingRowIndex], rows[endRowIndex].nextSibling ); } table.style.removeProperty('visibility'); document.removeEventListener('mousemove', mouseMoveHandler); document.removeEventListener('mouseup', mouseUpHandler); };});</script>大部分还是与上面讲过的一样,有一些略微差别而已,需要注意方向相关的就可以了。
还有就是动态创建的列表变成如下的样子了:
image.png
最后,放个效果图:
04111.gif
完整源码
传送门 [8] :https://gitee.com/ydydydq/dragging
链接:https://juejin.cn/post/7356326955930107904
作者:橙某人