JavaScript实现表格排序、编辑、拖拽与列宽缩放功能

作者:小圆点

时间:2026-10-29

浏览:262

本文介绍如何使用原生JavaScript实现HTML表格的高级交互功能,包括点击表头进行多类型排序、双击单元格进行自定义规则编辑、拖拽表头列进行顺序替换以及拖动边框调整列宽。代码包含完整的DOM操作、事件监听及兼容IE6的处理逻辑,适合需要轻量级表格交互解决方案的开发者参考。

文章目录

表格交互功能概述

实现简单的表格排序功能。

支持双击单元格进行编辑,并允许自定义编辑后的验证规则。

支持通过拖拽表头列实现列位置的交换与替换。

支持拖动列边框实现列宽度的动态缩放调整。

 

完整实现代码

具体实现代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>Table</title>
</head>
<style type="text/css">
body{ font-size:12px}
#tab{ border-collapse: collapse;}
.edit{ height:16px; width:98%; background-color:#EFF7FF; font-size:12px; border:0px;}
#tab thead td{ background:url(http://images.cnblogs.com/cnblogs_com/wtcsy/192373/r_t.bmp);color:#183C94;word-break:break-all}
#tab tbody td{overflow:hidden;word-break:break-all;}
#tab td{border: 1px solid #CECFCE;height:20px;line-height:20px;vertical-align:middle; }
#tab td.tc{text-align:center;}
.div{width:10px;height:6px; border:1px solid #999999; background-color:#FFFFFF; position:absolute; display:none;}
.line{ width:2px; background-color:#999999; position:absolute; display:none}
.dr{height:100%;width:2px;background:#CECFCE;float:right;margin-right:-1px;cursor:sw-resize}
.r{float:right;}
.l{float:left;}
#tab thead td.thover{ background-image:url(http://album.hi.csdn.net/app_uploads/wtcsy/20081126/000054336.p.gif);background-repeat:repeat-x;}
</style>
<body >
<table id="tab" border="0" cellspacing="1" cellpadding="0">
<thead>
<tr>
<td width="80"class="tc" ><span class="l">ID</span><div class="r dr"></div></td>
<td width="80"class="tc"><span class="l">选中</span><div class="r dr"></div></td>
<td width="130" class="tc"><span class="l">姓名</span><div class="r dr"></div></td>
<td width="130" class="tc" ><span class="l">生日</span><div class="r dr"></div></td>
<td width="220" class="tc" ><span class="l">备注</span><div class="r dr"></div></td>
</tr>
</thead>
<tbody>
<tr>
<td height="16">1</td>
<td><input type ="checkbox"><input name="ss" type="radio" /></td>
<td>小三</td>
<td>1982-05-27</td>
<td>杯具,全是杯具</td>
</tr>
<tr>
<td>3</td>
<td><input type ="checkbox"><input name="ss" type="radio" /></td>
<td>李四</td>
<td>1983-06-27</td>
<td>恩恩我魔兽技术不错</td>
</tr>
<tr>
<td>2</td>
<td><input type ="checkbox"><input name="ss" type="radio" /></td>
<td>王五</td>
<td>1987-05-27</td>
<td>波斯王子 时之刃还不错</td>
</tr>
<tr>
<td>4</td>
<td><input type ="checkbox"><input name="ss" type="radio" /></td>
<td>赵六</td>
<td>1988-05-27</td>
<td>我叫赵六</td>
</tr>
<tr>
<td>5</td>
<td><input type ="checkbox"><input name="ss" type="radio" /></td>
<td>朱八</td>
<td>1984-05-27</td>
<td>洗洗睡吧</td>
</tr>
<tr>
<td>6</td>
<td><input type ="checkbox"><input name="ss" type="radio" /></td>
<td>阿斯多夫</td>
<td>1984-06-27</td>
<td>阿斯多夫暗室逢灯</td>
</tr>
<tr>
<td>7</td>
<td><input type ="checkbox"><input name="ss" type="radio" /></td>
<td>杯具</td>
<td>1984-06-27</td>
<td>很多的杯具</td>
</tr>
<tr>
<td>8</td>
<td><input type ="checkbox"><input name="ss" type="radio" /></td>
<td>餐具</td>
<td>1984-02-27</td>
<td>很多的餐具</td>
</tr>
<tr>
<td>8</td>
<td><input type ="checkbox"><input name="ss" type="radio" /></td>
<td>洗具</td>
<td>1984-08-27</td>
<td>很多的洗具</td>
</tr>
<tr>
<td>9</td>
<td><input type ="checkbox"><input name="ss" type="radio" /></td>
<td>内牛满面</td>
<td>1984-12-27</td>
<td>10快一晚</td>
</tr>
<tr>
<td>10</td>
<td><input type ="checkbox"><input name="ss" type="radio" /></td>
<td>犀利哥</td>
<td>1984-12-21</td>
<td>嘿嘿</td>
</tr>
</tbody>
</table>
<script language="javascript">
(function(window,undefined){
window.Sys = function (ua){
var b = {
ie: /msie/.test(ua) && !/opera/.test(ua),
opera: /opera/.test(ua),
safari: /webkit/.test(ua) && !/chrome/.test(ua),
firefox: /firefox/.test(ua),
chrome: /chrome/.test(ua)
},vMark = "";
for (var i in b) {
if (b[i]) { vMark = "safari" == i ? "version" : i; break; }
    }
b.version = vMark && RegExp("(?:" + vMark + ")[\\/: ]([\\d.]+)").test(ua) ? RegExp.$1 : "0";
b.ie6 = b.ie && parseInt(b.version, 10) == 6;
b.ie7 = b.ie && parseInt(b.version, 10) == 7;
b.ie8 = b.ie && parseInt(b.version, 10) == 8;
return b;
}(window.navigator.userAgent.toLowerCase());
window.Sys.ie6&&document.execCommand("BackgroundImageCache", false, true);
window.$ = function(Id){
 return document.getElementById(Id);
};
window.addListener = function(element,e,fn){
 !element.events&&(element.events = {});
 element.events[e]&&(element.events[e][addListener.guid++]=fn)||(element.events[e] = {'0':fn});
 element.addEventListener?element.addEventListener(e,fn,false):element.attachEvent("on" + e,fn);
};
window.addListener.guid = 1;
window.removeListener = function(element,e,fn){
 var handlers = element.events[e],type;
 if(fn){
 for(type in handlers)
 if(handlers[type]===fn){
 element.removeEventListener?element.removeEventListener(e,fn,false):element.detachEvent("on" + e,fn);
 delete handlers[type];
 }
 }else{
 for(type in handlers){
 element.removeEventListener?element.removeEventListener(e,handlers[type],false):element.detachEvent("on" + e,handlers[type]);
 delete handlers[type];
 }
 } 
};
window.setStyle = function(e,o){
 if(typeof o=="string")
 e.style.cssText=o;
 else 
 for(var i in o)
 e.style[i] = o[i];
};
var slice = Array.prototype.slice;
window.Bind = function(object, fun) {
 var args = slice.call(arguments).slice(2);
 return function() {
 return fun.apply(object, args);
 };
};
window.BindAsEventListener = function(object, fun,args) {
 var args = slice.call(arguments).slice(2);
 return function(event) {
 return fun.apply(object, [event || window.event].concat(args));
 }
};
//copy from jQ
window.Extend = function(){
 var target = arguments[0] || {}, i = 1, length = arguments.length, deep = true, options;
 if ( typeof target === "boolean" ) {
 deep = target;
 target = arguments[1] || {};
 i = 2;
 }
 if ( typeof target !== "object" && Object.prototype.toString.call(target)!="[object Function]")
 target = {};
 for(;i<length;i++){
 if ( (options = arguments[ i ]) != null )
 for(var name in options){
 var src = target[ name ], copy = options[ name ];
 if ( target === copy )
 continue;
 if ( deep && copy && typeof copy === "object" && !copy.nodeType ){
 target[ name ] = arguments.callee( deep, src || ( copy.length != null ? [ ] : { } ), copy );
 } 
 else if(copy !== undefined)
 target[ name ] = copy; 
 }
 }
 return target; 
};
window.objPos = function(o){
 var x = 0, y = 0;
 do{x += o.offsetLeft;y += o.offsetTop;}while((o=o.offsetParent));
 return {'x':x,'y':y};
}
window.Class = function(properties){
 var _class = function(){return (arguments[0] !== null && this.initialize && typeof(this.initialize) == 'function') ? this.initialize.apply(this, arguments) : this;};
 _class.prototype = properties;
 return _class;
};
window.hasClass = function(element, className){ 
 return element.className.match(new RegExp('(\\s|^)'+className+'(\\s|$)')); 
} ;
window.addClass = function(element, className) { 
 !this.hasClass(element, className)&&(element.className += " "+className);
} 
window.removeClass = function(element, className) { 
 hasClass(element, className)&&(element.className = element.className.replace(new RegExp('(\\s|^)'+className+'(\\s|$)'),' ')); 
} 
})(window);
var Table = new Class({
 options :{
 minWidth : 62
 },
 initialize : function(tab,set){
 this.table = tab;
 this.rows = []; //里面记录所有tr的引用
 this.sortCol = null; //记录哪列正在排序中
 this.inputtd = null; //记录哪个td正在被编辑了
 this.editconfig = {}; //编辑表格的规则和提示
 this.thead = tab.getElementsByTagName('thead')[0];
 this.theadTds = tab.getElementsByTagName('thead')[0].getElementsByTagName('td'); //常常用到的dom集合可以用个属性来引用
 this.tbodyTds = tab.getElementsByTagName('tbody')[0].getElementsByTagName('td');
 this.closConfig = {
 on : false,
 td : null,
 totd : null
 };
 this.widthConfig = {
 td : null,
 nexttd : null,
 x : 0,
 tdwidth : 0,
 nexttdwidth : 0
 };
 Extend(this,this.options);
 //不知道原因 反正不设置就会乱跳
 (Sys.ie6||Sys.chrome)&&(tab.width=tab.offsetWidth)
 //记录那些checkbox,radio被选中了 ie6在做dom操作的时候不会记住这些状态
 if(Sys.ie6){
 this.checkbox = {}; 
 var checkboxs = tab.getElementsByTagName('input'),i=0,l=checkboxs.length;
 for(;i<l;i++)
 (checkboxs[i].type=="checkbox"||checkboxs[i].type=="radio")&&
 addListener(checkboxs[i],"click",Bind(this,function(elm,i){
 elm.checked==true?(this.checkbox[i] = elm):(delete this.checkbox[i]);
 },checkboxs[i],i)); 
 };
 var i=0,l=set.length,rows =tab.tBodies[0].rows,d=document,tabTads=tab.getElementsByTagName('td'),length=this.theadTds.length;
 //编辑用的input
 this.input = d.createElement('input'); 
 this.input.type = "text";
 this.input.className = 'edit';
 //用于显示正在拖拽的div
 this.div = d.body.appendChild(d.createElement('div'));
 this.div.className ="div";
 //进行缩放的时候显示的竖线
 this.line = d.body.appendChild(d.createElement('div'));
 this.line.className = 'line';
 this.line.style.top = objPos(tab).y +"px";
 //遍历set 做一些设置 
 for(;i<l;i++){
 //给需要排序的猎头绑定事件
 addListener(this.theadTds[set[i].id],'click',Bind(this,this.sortTable,this.theadTds[set[i].id],set[i].type));
 //给需要编辑的表给列定义所需配置
 set[i].edit&&(this.editconfig[set[i].id]={rule:set[i].edit.rule,message:set[i].edit.message});
 }
 //把 所有的tr放到一个数组 用于排序 
 for( i=0,l=rows.length;i<l;i++)
 this.rows[i]=rows[i];
 
 //遍历所有的td 做一些设置 
 for( i=0,l=tabTads.length;i<l;i++){
 //将头部的td全部做上标记 拖拽的时候要用到
 i<length&&tabTads[i].setAttribute('clos',i);
 //将需要编辑的td添加edit属性
 i>=length&&this.editconfig[i%length]&&tabTads[i].setAttribute('edit',i%length);
 }
 
 //绑定 拖拽 和缩放的操作
 addListener(this.thead,'mousedown',BindAsEventListener(this,this.dragOrWidth));
 
 //拖拽的时候 记录移动到了那列td上
 addListener(this.thead,'mouseover',BindAsEventListener(this,this.theadHover));
 
 //唉
 addListener(this.thead,'mouseout',BindAsEventListener(this,this.theadOut));
 
 //绑定编辑事件 根据e.srcElement or e.target去判断是哪个表格被编辑 
 addListener(tab,'dblclick',BindAsEventListener(this,this.edit)); 
 
 //当离开input时候保存下修改的内容
 addListener(this.input,'blur',Bind(this,this.save,this.input)); 
 },
 sortTable :function(td,type){ //td为点击的那个元素 n 为哪一列进行排序 type为进行什么类型的排序
 var frag=document.createDocumentFragment(),span=td.getElementsByTagName('span')[0],str= span.innerHTML;
 if(td===this.sortCol){
 this.rows.reverse();
 span.innerHTML =str.replace(/.$/,str.charAt(str.length-1)=="↓"?"↑":"↓") ;
 }else{
 this.rows.sort(this.compare(td.getAttribute('clos'),type));
 span.innerHTML = span.innerHTML + "↑";
 this.sortCol!=null&&(this.sortCol.getElementsByTagName('span')[0].innerHTML = this.sortCol.getElementsByTagName('span')[0].innerHTML.replace(/.$/,''));//把之前那列排序的标识去掉
 };
 for(var i=0,l=this.rows.length;i<l;i++)
 frag.appendChild(this.rows[i]);
 this.table.tBodies[0].appendChild(frag);
 if(Sys.ie6){
 for(var s in this.checkbox)
 this.checkbox[s].checked = true;
 }
 this.sortCol = td; //记录哪一列正在排序中 
 },
 compare :function(n,type){
 return function (a1,a2){
 var convert ={
 int : function(v){return parseInt(v)},
 float : function(v){return parseFloat(v)},
 date : function(v){return v.toString()},
 string : function(v){return v.toString()}
 };
 !convert[type]&&(convert[type]=function(v){return v.toString()});
 a1 =convert[type](a1.cells[n].innerHTML);
 a2 =convert[type](a2.cells[n].innerHTML);
 return a1==a2?0:a1<a2?-1:1; 
 };
 },
 edit: function(e){
 var elem = this.inputtd=e.srcElement || e.target;
 if(!elem.getAttribute('edit'))return;
 this.input.value = elem.innerHTML;
 elem.innerHTML = "";
 elem.appendChild(this.input);
 this.input.focus();
 },
 save : function(elem){
 var editinfo=this.editconfig[elem.parentNode.getAttribute('edit')],status={
 "[object Function]" : 'length' in editinfo.rule&&editinfo.rule(this.input.value)||false, 
 "[object RegExp]" : 'test' in editinfo.rule&&editinfo.rule.test(this.input.value)||false
 }[Object.prototype.toString.call(editinfo.rule)],_self=this;
 //如果不符合条件 修改提示信息
 typeof status != "boolean"&&(editinfo.message = status);
 if(status===true){
 this.inputtd.innerHTML = this.input.value;
 this.inputtd=null;
 }else{
 alert(editinfo.message);
 //firefox下 直接用input.focus()不会执行 用setTimeout可以执行
 setTimeout(function(){_self.input.focus()},0);
 } 
 },
 theadHover : function(e){
 var elem = e.srcElement || e.target;
 if(elem.nodeName.toLowerCase() ==='td'&&this.closConfig.on){
 this.closConfig.totd = elem.getAttribute('clos');
 !hasClass(elem,'thover')&&addClass(elem,'thover');
 } 
 },
 theadOut : function(e){
 var elem = e.srcElement || e.target;
 if(elem.nodeName.toLowerCase() ==='td'&&this.closConfig.on)removeClass(elem,'thover')
 },
 dragOrWidth : function(e){
 var elem = e.srcElement || e.target,widthConfig=this.widthConfig;
 
 //执行拖拽
 if(elem.nodeName.toLowerCase()==='td'){
 this.closConfig.td = elem.getAttribute('clos');
 addListener(document,'mousemove',BindAsEventListener(this,this.dragMove));
 addListener(document,'mouseup',Bind(this,this.dragUp));
 this.closConfig.on = true;
 Sys.ie?this.thead.setCapture(false):e.preventDefault();
 }
 
 //执行宽度缩放
 if(elem.nodeName.toLowerCase()==='div'){
 Sys.ie?(e.cancelBubble=true):e.stopPropagation();
 //如果是最后一个td里面的div 不进行缩放
 if(this.theadTds[this.theadTds.length-1]===elem.parentNode)return
 Sys.ie?this.thead.setCapture(false):e.preventDefault();
 widthConfig.x = e.clientX;
 widthConfig.td = elem.parentNode;
 widthConfig.nexttd = widthConfig.td.nextSibling;
 while(widthConfig.nexttd.nodeName.toLowerCase()!="td"){
 widthConfig.nexttd = widthConfig.nexttd.nextSibling;
 };
 widthConfig.tdwidth = widthConfig.td.offsetWidth;
 widthConfig.nexttdwidth = widthConfig.nexttd.offsetWidth;
 this.line.style.height = this.table.offsetHeight +"px";
 addListener(document,'mousemove',BindAsEventListener(this,this.widthMove));
 addListener(document,'mouseup',Bind(this,this.widthUp)); 
 }
 },
 dragMove : function(e){
 window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
 setStyle(this.div,{display:"block",left:e.clientX+9+"px",top:e.clientY+20+"px"});
 },
 dragUp :function(){
 var closConfig = this.closConfig,rows = this.table.getElementsByTagName('tr'),td,n,o,i=0,l=rows.length;
 this.div.style.display = "none";
 removeListener(document,'mousemove');
 removeListener(document,'mouseup');
 Sys.ie&&this.thead.releaseCapture();
 closConfig.on = false; 
 if(closConfig.totd===null)return;
 removeClass(this.theadTds[closConfig.totd],'thover');
 //在同一列 不进行列替换
 if(closConfig.td === closConfig.totd)return;
 
 //进行列替换 如果
 if(closConfig.td*1+1===closConfig.totd*1){
 n = closConfig.totd;
 o = closConfig.td;
 }else{
 n = closConfig.td;
 o = closConfig.totd;
 }
 for(;i<l;i++){
 td = rows[i].getElementsByTagName('td');
 rows[i].insertBefore(td[n],td[o]);
 } 
 
 //重新标识表头
 for(i=0,l=this.theadTds.length;i<l;i++)
 this.theadTds[i].setAttribute('clos',i);
 closConfig.totd=closConfig.td=null; 
 },
 widthMove : function(e){
 window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
 var widthConfig = this.widthConfig,x = e.clientX - widthConfig.x,left = e.clientX,clientX=left;
 if(clientX<widthConfig.x&&widthConfig.x - clientX>widthConfig.tdwidth-this.minWidth){
 left = widthConfig.x - widthConfig.tdwidth+this.minWidth;
 }
 if(clientX>widthConfig.x&&clientX - widthConfig.x>widthConfig.nexttdwidth-this.minWidth){
 left =widthConfig.x + widthConfig.nexttdwidth-this.minWidth; 
 }
 setStyle(this.line,{display:"block",left:left+"px"}); 
 },
 widthUp : function(){
 this.line.style.display = "none";
 var widthConfig = this.widthConfig,x= parseInt(this.line.style.left) - widthConfig.x; 
 widthConfig.nexttd.style.width = widthConfig.nexttdwidth -x -1 +'px';
 widthConfig.td.style.width = widthConfig.tdwidth + x -1 +'px';
 Sys.ie&&this.thead.releaseCapture();
 removeListener(document,'mousemove');
 removeListener(document,'mouseup');
 } 
});
window.onload = function(){
 function checkName(val){
 if(val.replace(/^\s+$/g,'')==='') return '姓名输入不能为空';
 if(val.replace(/^\s+|\s+$/,'').length>10) return '姓名长度不能大于10个字符';
 if(!/^[\u4e00-\u9fa5a-z]+$/i.test(val)) return '姓名只能输入中文或者是字母';
 return true;
 };
 function checkRemark(val){
 if(val.replace(/^\s+$/g,'')==='') return '备注输入不能为空';
 if(val.replace(/^\s+|\s+$/,'').length>15) return '备注长度不能大于15个字符';
 if(!/^[\u4e00-\u9fa5\w\s]+$/i.test(val)) return '备注只能输入中文数字下划线空格';
 return true;
 }
 var set = [
 {id:0,type:"int"},
 {id:2,type:"string",edit:{rule:checkName,message:''}},
 {id:3,type:"date",edit:{rule:/^\d{4}\-\d{2}\-\d{2}$/,message:"按这中格式输入日期 1985-02-30"}},
 {id:4,type:"string",edit:{rule:checkRemark,message:''}}
 ];
 new Table($("tab"),set);
}
</script>

已知兼容性问题

已知BUG:

ie6下 中文不自动换行

非ie下字母和数字也不自动换行确实让人恼火

chrome浏览器下点击运行好像问题很大 拿到本地测试会比较好

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章