本文介绍一种高效的JavaScript表格行拖拽实现方案。通过mousedown记录初始状态,利用document.elementFromPoint(x,y)实时获取鼠标下方元素,避免复杂的坐标区间计算。结合insertBefore实现行交换,显著提升性能,适用于IE及Chrome等浏览器。
传统坐标计算方案的局限性
实现表格行拖拽的核心逻辑直观:选中某一行后,向上拖动则与其上方相邻行交换位置,向下拖动则与下方相邻行交换。关键难点在于如何准确识别目标交换行。曾见过一种详尽教程,通过计算表格中每一行的高度与Y轴坐标,判断鼠标事件对象的pageX是否落在[rowTop, rowBottom]区间内。这种方法虽然直接,但存在明显局限:行数越多,需要计算的区间就越多,导致计算量巨大。其实现方式通常是获取事件源对象,向上遍历父元素,若父元素为TR标签,则提取其对应的坐标区间。这种思路直接且客观,但也带来一个硬性限制:无法使用代理元素进行拖动。无论拖动的是代理对象还是实物,在鼠标移动过程中都需要进行大量的坐标运算,尽管这在IE浏览器中尚能承受,但性能并非最优。是否存在更优的解决方案?
基于elementFromPoint的高效实现
人们往往因掌握的技能有限而固守旧有方法,若拥有更多原生API工具,便能做出更优选择。熟悉并掌握更多原生API有助于获取更高效的实现路径。换个思路,在mousedown事件触发时,保存当前行及其样式与坐标信息;在拖动过程中,利用document.elementFromPoint(x,y)这一巧妙API获取鼠标当前位置下的DOM元素。通常情况下,该API返回的是TD元素;若在TD内嵌套了DIV,则返回DIV元素。接着,获取该元素的父级TR元素,并判断其是否与mousedown时保存的TR元素相同。若不同,则执行后续操作:通过简单的减法判断是向上还是向下移动,随后利用insertBefore方法交换两行位置。最后在mouseup事件中还原行的样式。由于避免了复杂的坐标区间计算,整个程序运行出奇地高效。
完整演示代码
完整演示代码
<!doctype html>
<html>
<head>
<title>表格行拖动</title>
<script>
window.onload = function(){
//绑定事件
var addEvent = document.addEventListener ? function(el,type,callback){
el.addEventListener( type, callback, !1 );
} : function(el,type,callback){
el.attachEvent( "on" + type, callback );
}
//移除事件
var removeEvent = document.removeEventListener ? function(el,type,callback){
el.removeEventListener( type, callback );
} : function(el,type,callback){
el.detachEvent( "on" + type, callback);
}
//精确获取样式
var getStyle = document.defaultView ? function(el,style){
return document.defaultView.getComputedStyle(el, null).getPropertyValue(style)
} : function(el,style){
style = style.replace(/\-(\w)/g, function($, $1){
return $1.toUpperCase();
});
return el.currentStyle[style];
}
var dragManager = {
clientY:0,
draging:function(e){//mousemove时拖动行
var dragObj = dragManager.dragObj;
if(dragObj){
e = e || event;
if(window.getSelection){//w3c
window.getSelection().removeAllRanges();
}else if(document.selection){
document.selection.empty();//IE
}
var y = e.clientY;
var down = y > dragManager.clientY;//是否向下移动
var tr = document.elementFromPoint(e.clientX,e.clientY);
if(tr && tr.nodeName == "TD"){
tr = tr.parentNode
dragManager.clientY = y;
if( dragObj !== tr){
tr.parentNode.insertBefore(dragObj, (down ? tr.nextSibling : tr));
}
};
}
},
dragStart:function(e){
e = e || event;
var target = e.target || e.srcElement;
if(target.nodeName === "TD"){
target = target.parentNode;
dragManager.dragObj = target;
if(!target.getAttribute("data-background")){
var background = getStyle(target,"background-color");
target.setAttribute("data-background",background)
}
//显示为可移动的状态
target.style.backgroundColor = "#ccc";
target.style.cursor = "move";
dragManager.clientY = e.clientY;
addEvent(document,"mousemove",dragManager.draging);
addEvent(document,"mouseup",dragManager.dragEnd);
}
},
dragEnd:function(e){
var dragObj = dragManager.dragObj
if (dragObj) {
e = e || event;
var target = e.target || e.srcElement;
if(target.nodeName === "TD"){
target = target.parentNode;
dragObj.style.backgroundColor = dragObj.getAttribute("data-background");
dragObj.style.cursor = "default";
dragManager.dragObj = null;
removeEvent(document,"mousemove",dragManager.draging);
removeEvent(document,"mouseup",dragManager.dragEnd);
}
}
},
main:function(el){
addEvent(el,"mousedown",dragManager.dragStart);
}
}
var el = document.getElementById("table");
dragManager.main(el);
}
</script>
<style>
.table{
width:60%;
border: 1px solid red;
border-collapse: collapse;
}
.table td{
border: 1px solid red;
height: 20px;
}
</style>
</head>
<body>
<h1>表格行拖动</h1>
<table id="table" class="table">
<tbody>
<tr>
<td>1</td>
<td>One</td>
<td>dom.require</td>
</tr>
<tr id="2" >
<td class="2">2</td>
<td>Two</td>
<td>ControlJS </td>
</tr>
<tr id="3" >
<td class="3">3</td>
<td>Three</td>
<td>HeadJS</td>
</tr>
<tr id="4" >
<td class="4">4</td>
<td>Four</td>
<td>LAB.js</td>
</tr>
<tr id="5" >
<td class="5">5</td>
<td>Five</td>
<td>$script.js</td>
</tr>
<tr id="6" >
<td class="6">6</td>
<td>Six</td>
<td>NBL.js</td>
</tr>
</tbody>
</table>
</body>
</html>
核心代码
核心代码实现
window.onload = function(){
//绑定事件
var addEvent = document.addEventListener ? function(el,type,callback){
el.addEventListener( type, callback, !1 );
} : function(el,type,callback){
el.attachEvent( "on" + type, callback );
}
//移除事件
var removeEvent = document.removeEventListener ? function(el,type,callback){
el.removeEventListener( type, callback );
} : function(el,type,callback){
el.detachEvent( "on" + type, callback);
}
//精确获取样式
var getStyle = document.defaultView ? function(el,style){
return document.defaultView.getComputedStyle(el, null).getPropertyValue(style)
} : function(el,style){
style = style.replace(/\-(\w)/g, function($, $1){
return $1.toUpperCase();
});
return el.currentStyle[style];
}
var dragManager = {
clientY:0,
draging:function(e){//mousemove时拖动行
var dragObj = dragManager.dragObj;
if(dragObj){
e = e || event;//清除选区
if(window.getSelection){//w3c
window.getSelection().removeAllRanges();
}else if(document.selection){
document.selection.empty();//IE
}
var y = e.clientY;
var down = y > dragManager.clientY;//是否向下移动
var tr = document.elementFromPoint(e.clientX,e.clientY);
if(tr && tr.nodeName == "TD"){
tr = tr.parentNode
dragManager.clientY = y;
if( dragObj !== tr){
tr.parentNode.insertBefore(dragObj, (down ? tr.nextSibling : tr));
}
};
}
},
dragStart:function(e){
e = e || event;
var target = e.target || e.srcElement;
if(target.nodeName === "TD"){
target = target.parentNode;
dragManager.dragObj = target;
if(!target.getAttribute("data-background")){
var background = getStyle(target,"background-color");
target.setAttribute("data-background",background)
}
//显示为可移动的状态
target.style.backgroundColor = "#ccc";
target.style.cursor = "move";
dragManager.clientY = e.clientY;
addEvent(document,"mousemove",dragManager.draging);
addEvent(document,"mouseup",dragManager.dragEnd);
}
},
dragEnd:function(e){
var dragObj = dragManager.dragObj
if (dragObj) {
e = e || event;
var target = e.target || e.srcElement;
if(target.nodeName === "TD"){
target = target.parentNode;
dragObj.style.backgroundColor = dragObj.getAttribute("data-background");
dragObj.style.cursor = "default";
dragManager.dragObj = null;
removeEvent(document,"mousemove",dragManager.draging);
removeEvent(document,"mouseup",dragManager.dragEnd);
}
}
},
main:function(el){
addEvent(el,"mousedown",dragManager.dragStart);
}
}
var el = document.getElementById("table");
dragManager.main(el);
}
实现代码二
第二种实现方式
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head >
<title>无标题页</title>
</head>
<body>
<table id="tb1" border="1" cellpadding="3">
<tr>
<th>移动</th>
<th>数据</th>
<th>数据</th>
<th>数据</th>
</tr>
<tr>
<td>*</td>
<td>11111111111</td>
<td>sdgergserhserhserh</td>
<td>awegaw</td>
</tr>
<tr>
<td>*</td>
<td>222222222</td>
<td>serherwwwwww</td>
<td>fafafff</td>
</tr>
<tr>
<td>*</td>
<td>333333333</td>
<td>qqqqqqqwewer</td>
<td>yukyuk</td>
</tr>
</table>
<script type="text/javascript">
var curTr = null;
var tb1 = document.getElementById('tb1');
var trs = tb1.getElementsByTagName('tr');
tb1.onselectstart = function(){
if(curTr){
document.selection.empty(); return true;
}
};
for(var i=1; i<trs.length; i++) {
var tds = trs[i].getElementsByTagName('td');
tds[0].style.cursor = 'move';
tds[0].onmousedown = function(){
curTr = this.parentNode;
curTr.style.backgroundColor = '#eff';
};
tds[0].onmouseover = function() {
if(curTr && curTr != this.parentNode) {
this.parentNode.swapNode(curTr);
}
}
}
document.body.onmouseup = function(){
if(curTr){
curTr.style.backgroundColor = '';
curTr = null;
}
};
</script>
</body>
</html>
注:不兼容firefox,在IE和chrome下测试通过哦。