JavaScript 实现可拖动列表排序的完整代码示例

作者:纸飞机

时间:2026-10-26

浏览:103

本文详细介绍如何使用原生 JavaScript 实现可拖动列表排序功能。通过克隆节点、绝对定位跟随鼠标以及判断插入位置等步骤,展示完整的 HTML 结构与 JS 逻辑代码,包括处理 Firefox 浏览器用户选择禁用的 CSS 技巧。

文章目录

禁止拖动时选中文字的处理

补充说明:


为防止在拖动过程中选中文字,Firefox 浏览器可以通过设置 CSS 来实现。


xxxx{-moz-user-select:none;}


其他浏览器则需要通过 JavaScript 进行设置。


XXXX.onselectstart = function(){return false}


可拖动列表的实现原理

其实现原理是:当点击目标元素后,创建一个占位元素(或者复制一份目标元素,记为元素 B),然后拖动目标元素(或新创建的元素 B)。


找到合适的插入位置后,将目标元素插入,并清除占位元素或元素 B。


例如,假设有一个列表:


代码如下:

 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<style type="text/css">
*{ margin: 0; padding: 0; -moz-user-select:none;}
ul{ margin: 100px; border: 1px solid #adff2f; list-style: none;}
li{ height: 30px; line-height: 30px; padding: 0; list-style: none;}
li:hover{ background: #bdb76b; cursor: move;}
</style>
</head>
<body>
<ul id="outer_wrap">
<li>第一条</li>
<li>第二条</li>
<li>第三条</li>
<li>第四条</li>
<li>第五条</li>
<li>第六条</li>
<li>第七条</li>
<li>第八条</li>
</ul>
</body>
</html>

点击“<li>第一条</li>”(记为元素 A),获取其位置和尺寸信息,将元素 A 克隆一份(记为元素 B),插入文档中,并将元素 B 绝对定位到元素 A 的位置。随后在鼠标移动时,元素 B 跟随拖动,接着判断插入点。


判断插入点即判断当前鼠标所在的坐标。如果鼠标坐标落在除了元素 A 之外的其他 LI 元素范围内,则满足可插入条件。此时,若鼠标位于元素上半部分,则插入到该元素之前;若位于下半部分,则插入到该元素之后。(个人觉得这段描述略显复杂)


插入点判断与完整代码示例

禁止拖动时选中文字的处理 为了标示出插入的位置,我们在选中的插入点处另插入一个元素,仅作为标杆使用。下面是一个 demo:

代码如下:

 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<style type="text/css">
*{ margin: 0; padding: 0; -moz-user-select:none;}
ul{ margin: 100px; border: 1px solid #adff2f; list-style: none;}
li{ height: 30px; line-height: 30px; padding: 0; list-style: none;}
li:hover{ background: #bdb76b; cursor: move;}
</style>
</head>
<body>
<ul id="outer_wrap">
<li>第一条</li>
<li>第二条</li>
<li>第三条</li>
<li>第四条</li>
<li>第五条</li>
<li>第六条</li>
<li>第七条</li>
<li>第八条</li>
</ul>
<script type="text/javascript">
function $(id){
return document.getElementById(id);
} 
//获取鼠标位置
function getMousePos(e){
return {
x : e.pageX || e.clientX + document.body.scrollLeft,
y : e.pageY || e.clientY + document.body.scrollTop
} 
} 
//获取元素位置
function getElementPos(el){
return {
x : el.offsetParent ? el.offsetLeft + arguments.callee(el.offsetParent)['x'] : el.offsetLeft,
y : el.offsetParent ? el.offsetTop + arguments.callee(el.offsetParent)['y'] : el.offsetTop
} 
} 
//获取元素尺寸
function getElementSize(el){
return {
width : el.offsetWidth,
height : el.offsetHeight
} 
} 
//禁止选择
document.onselectstart = function(){
return false;
} 
//判断是否有挪动
var MOVE = {};
MOVE.isMove = false;
//就是创建的标杆
var div = document.createElement('div');
div.style.width = '400px';
div.style.height = '1px';
div.style.fontSize = '0';
div.style.background = 'red';
var outer_wrap = $('outer_wrap');
outer_wrap.onmousedown = function(event){
//获取列表顺序
var lis = outer_wrap.getElementsByTagName('li');
for(var i = 0; i < lis.length; i++){
lis[i]['pos'] = getElementPos(lis[i]);
lis[i]['size'] = getElementSize(lis[i]);
} 
event = event || window.event;
var t = event.target || event.srcElement;
if(t.tagName.toLowerCase() == 'li'){
var p = getMousePos(event);
var el = t.cloneNode(true);
el.style.position = 'absolute';
el.style.left = t.pos.x + 'px';
el.style.top = t.pos.y + 'px';
el.style.width = t.size.width + 'px';
el.style.height = t.size.height + 'px';
el.style.border = '1px solid #d4d4d4';
el.style.background = '#d4d4d4';
el.style.opacity = '0.7';
document.body.appendChild(el);
document.onmousemove = function(event){
event = event || window.event;
var current = getMousePos(event);
el.style.left =t.pos.x + current.x - p.x + 'px';
el.style.top =t.pos.y + current.y - p.y+ 'px';
document.body.style.cursor = 'move';
//判断插入点
for(var i = 0; i < lis.length; i++){
if(current.x > lis[i]['pos']['x'] && current.x < lis[i]['pos']['x'] + lis[i]['size']['width'] && current.y > lis[i]['pos']['y'] && current.y < lis[i]['pos']['y'] + lis[i]['size']['height']/2){
if(t != lis[i]){
MOVE.isMove = true;
outer_wrap.insertBefore(div,lis[i]);
} 
}else if(current.x > lis[i]['pos']['x'] && current.x < lis[i]['pos']['x'] + lis[i]['size']['width'] && current.y > lis[i]['pos']['y'] + lis[i]['size']['height']/2 && current.y < lis[i]['pos']['y'] + lis[i]['size']['height']){
if(t != lis[i]){
MOVE.isMove = true;
outer_wrap.insertBefore(div,lis[i].nextSibling);
} 
} 
} 
} 
//移除事件
document.onmouseup = function(event){
event = event || window.event;
document.onmousemove = null;
if(MOVE.isMove){
outer_wrap.replaceChild(t,div);
MOVE.isMove = false;
} 
document.body.removeChild(el);
el = null;
document.body.style.cursor = 'normal';
document.onmouseup = null;
} 
} 
} 
</script>
</body>
</html>

上面的 demo 代码细节欠缺考虑,仅供展示原理。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章