表头固定内容可滚动表格的3种实现方法
有时候,我们在开发前端页面过程中,可能会用到这种表格:表头固定不动,表格内容(<tbody>)需要竖直滚动。
像这样的:
还有这样的:
通过研究,我大致总结了以下三种实现办法供大家参考,如果有错误之处敬请指正,也欢迎拍砖!!
这种实现方法最简单,只需要用两个表格,一个表格作为表头,另一个表格用<div>包裹并设置该<div>的高度固定,高度溢出可滚动即可。两个表格的列宽用相同的<colgroup>固定列宽值,需要注意的是滚动条会占用一定的宽度,一般是:17px,所以在表头需要特别处理一下,不然表格就不能对齐了。大家看代码,最简单的办法是表头单独空一列和下面的表格滚动条对齐。
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <style> table th,table td{ border:1px solid #aaa; } table{ border-collapse:collapse; } </style> </head> <body> <div class="tableWrap" style="margin:20px;width:316px;"> <label>可滚动表格——表格宽度自动每列宽度设置<col></label> <table class="table-thead"> <colgroup> <col width="50"> <col width="100"> <col width="150"> <col width="17"> </colgroup> <thead> <tr> <th>序号</th> <th>账户名称</th> <th>账户编号</th> <th></th> </tr> </thead> </table> <div class="comTbody" style="height:90px;overflow:auto;border:1px solid #ddd"> <table class="table-tbody" style="border-top: 0;"> <colgroup> <col width="50"> <col width="100"> <col width="150"> </colgroup> <tbody> <tr> <td>1</td> <td>中国电信</td> <td>12312312313132</td> </tr> <tr> <td>1</td> <td>中国电信</td> <td>12312312313132</td> </tr> <tr> <td>1</td> <td>中国电信</td> <td>12312312313132</td> </tr> <tr> <td>1</td> <td>中国电信</td> <td>12312312313132</td> </tr> <tr> <td>1</td> <td>中国电信</td> <td>12312312313132</td> </tr> </tbody> </table> </div> </div> </body> </html>
第一种方法是我自己为了快速实现采用的办法,过后我想起来经常看到的数据表格(dataGrid),也是表头固定,内容可以滚动,比如嘴上那两张图片分别是:kendo UI——grid、easyUI——dataGrid 。这样的表格看起来挺漂亮的,我研究了一下,发现它们两种有各自的实现方式。这两种表格在实际运用中因为绑定了很多JS事件,所以表格嵌套的层级很多,下面我为了更清晰的展示给大家,简化了嵌套。
<label>可滚动表格——表格宽度100%,列宽度设置<col></label> <div class="table-wrap"> <div class="table-head"> <div class="table-head-wrap"> <table class="grid"> <colgroup> <col style="width:80px"> <col> <col> <col style="width:150px"> </colgroup> <thead> <tr> <th> <span class="tab-link">序号</span> </th> <th> <span class="tab-link">姓名</span> </th> <th> <span class="tab-link">年龄</span> </th> <th> <span class="tab-link">地址</span> </th> </tr> </thead> </table> </div> </div> <div class="table-content"> <table class="grid"> <colgroup> <col style="width:80px"> <col> <col> <col style="width:150px"> </colgroup> <tbody> <tr> <td> <div>001</div> </td> <td> <div>小明</div> </td> <td> <div>23</div> </td> <td> <div>上海</div> </td> </tr> <tr> <td> <div>001</div> </td> <td> <div>小明</div> </td> <td> <div>23</div> </td> <td> <div>上海</div> </td> </tr> <tr> <td> <div>001</div> </td> <td> <div>小明</div> </td> <td> <div>23</div> </td> <td> <div>上海</div> </td> </tr> <tr> <td> <div>001</div> </td> <td> <div>小明</div> </td> <td> <div>23</div> </td> <td> <div>上海</div> </td> </tr> <tr> <td> <div>001</div> </td> <td> <div>小明</div> </td> <td> <div>23</div> </td> <td> <div>上海</div> </td> </tr> </tbody> </table> </div> </div>
这里表格添加了一个样式:table-layout: fixed; 列宽由表格宽度和列宽度设定。这种方法适用于,表格宽度固定。
<div class="data-grid"> <div class="data-view"> <div class="grid-head"> <div class="grid-head-inner"> <table class="data-table"> <tbody> <tr class="data-table-row"> <td> <div class="datagrid-cell cell-c1"> <div>Item ID</div> </div> </td> <td> <div class="datagrid-cell cell-c2"> <div>Product</div> </div> </td> <td> <div class="datagrid-cell cell-c3"> <div>List Price</div> </div> </td> <td> <div class="datagrid-cell cell-c4"> <div>Unit Cost</div> </div> </td> <td> <div class="datagrid-cell cell-c5"> <div>Attribute</div> </div> </td> </tr> </tbody> </table> </div> </div> <div class="grid-body"> <table class="datagrid-btable"> <tbody> <tr> <td> <div class="datagrid-cell cell-c1"> <span>EST-1</span> </div> </td> <td> <div class="datagrid-cell cell-c2"> <span>FI-SW-01</span> </div> </td> <td> <div class="datagrid-cell cell-c3"> <span>36.5</span> </div> </td> <td> <div class="datagrid-cell cell-c4"> <span>10</span> </div> </td> <td> <div class="datagrid-cell cell-c5"> <span>Large</span> </div> </td> </tr> <tr> <td> <div class="datagrid-cell cell-c1"> <span>EST-1</span> </div> </td> <td> <div class="datagrid-cell cell-c2"> <span>FI-SW-01</span> </div> </td> <td> <div class="datagrid-cell cell-c3"> <span>36.5</span> </div> </td> <td> <div class="datagrid-cell cell-c4"> <span>10</span> </div> </td> <td> <div class="datagrid-cell cell-c5"> <span>Large</span> </div> </td> </tr> <tr> <td> <div class="datagrid-cell cell-c1"> <span>EST-1</span> </div> </td> <td> <div class="datagrid-cell cell-c2"> <span>FI-SW-01</span> </div> </td> <td> <div class="datagrid-cell cell-c3"> <span>36.5</span> </div> </td> <td> <div class="datagrid-cell cell-c4"> <span>10</span> </div> </td> <td> <div class="datagrid-cell cell-c5"> <span>Large</span> </div> </td> </tr> <tr> <td> <div class="datagrid-cell cell-c1"> <span>EST-1</span> </div> </td> <td> <div class="datagrid-cell cell-c2"> <span>FI-SW-01</span> </div> </td> <td> <div class="datagrid-cell cell-c3"> <span>36.5</span> </div> </td> <td> <div class="datagrid-cell cell-c4"> <span>10</span> </div> </td> <td> <div class="datagrid-cell cell-c5"> <span>Large</span> </div> </td> </tr> <tr> <td> <div class="datagrid-cell cell-c1"> <span>EST-1</span> </div> </td> <td> <div class="datagrid-cell cell-c2"> <span>FI-SW-01</span> </div> </td> <td> <div class="datagrid-cell cell-c3"> <span>36.5</span> </div> </td> <td> <div class="datagrid-cell cell-c4"> <span>10</span> </div> </td> <td> <div class="datagrid-cell cell-c5"> <span>Large</span> </div> </td> </tr> <tr> <td> <div class="datagrid-cell cell-c1"> <span>EST-1</span> </div> </td> <td> <div class="datagrid-cell cell-c2"> <span>FI-SW-01</span> </div> </td> <td> <div class="datagrid-cell cell-c3"> <span>36.5</span> </div> </td> <td> <div class="datagrid-cell cell-c4"> <span>10</span> </div> </td> <td> <div class="datagrid-cell cell-c5"> <span>Large</span> </div> </td> </tr> <tr> <td> <div class="datagrid-cell cell-c1"> <span>EST-1</span> </div> </td> <td> <div class="datagrid-cell cell-c2"> <span>FI-SW-01</span> </div> </td> <td> <div class="datagrid-cell cell-c3"> <span>36.5</span> </div> </td> <td> <div class="datagrid-cell cell-c4"> <span>10</span> </div> </td> <td> <div class="datagrid-cell cell-c5"> <span>Large</span> </div> </td> </tr> </tbody> </table> </div> </div> </div>
这种方法适用于每列宽度固定的表格。
终极做法:亲测可用
原理:在表格的前面追加一个ul,当页面滚动的时候,ul设置显示,并且在里面追加内容li,设置每一个li的宽度与表格的thead中对应的th的宽度一致,如果页面为自适应的,需要添加resize的事件,当页面resize时同时设置li的宽度
jsp/html:
<div class="tbl scroll-tbl"> <ul class="th-fixed"></ul> <table class="table table-striped table-bordered g-tbl" id="tbl"> </div>
css
.scroll-tbl{position:relative} /* 固定表头 */ ul.th-fixed{ display: none; position: absolute; top:0; color:#fff; border-left: 1px solid rgb(221, 221, 221); background-color: rgb(64, 173, 242); text-align: center; white-space: nowrap; } ul.th-fixed li{ padding: 8px; line-height: 1.42857143; display: inline-block; float:initial; border: 1px solid #ddd; } ul.th-fixed li:first-child{border-left: 1px solid transparent;} ul.th-fixed li+li{border-left:none;}
js
这里只是在构建表格的时候同时构建ul里面的内容(其余代码可忽略)
function initTableYdata(rowData,legendData,legendData1,type){ var tableData=[],hstr="",bstr="",ulList="<li>设备名称</li>"; $("#tbl").html(""); for(var i=0;i<treeCodeArr.length;i++){ var obj={}; obj.code=treeCodeArr[i].code; obj.name=treeCodeArr[i].name; obj.data=[]; obj.data.push(rowData[0].data[i]); obj.data.push(rowData[1].data[i]); tableData.push(obj); } /*构建表格*/ hstr="<thead><tr class='yel'><th>设备名称</th>"; for(var j=0;j<legendData.length;j++){ hstr+=type==0?"<th>"+legendData1[j]+"</th>":"<th>"+legendData[j]+"</th>"; ulList+=type==0?"<li>"+legendData1[j]+"</li>":"<li>"+legendData[j]+"</li>"; } $(".tbl .th-fixed").html(""); $(".tbl .th-fixed").html(ulList); hstr+="</tr></thead>"; bstr="<tbody>"; for(var m=0;m<tableData.length;m++){ var _data=tableData[m].data; bstr+="<tr><td>"+tableData[m].name+"</td>" for(var n=0;n<_data.length;n++){ bstr+="<td>"+_data[n]+"</td>" } bstr+="</tr>" } bstr+="</tbody>"; str=hstr+bstr; $("#tbl").append(str); //如果页面有自适应需要加上这里,不然自适应之后ul头部不会自适应,因为我的页面在resize的时候重新构建了表格 $('ul.th-fixed').each(function(index,ele){ setUlWidth($(ele),true); }); //动画的效果 showTable(); }
重要部分:
为需要滚动的部分添加滚轮事件,
$(function(){ $('.scroll-tbl').on('scroll',function(){ var top=$(this)[0].scrollTop; var el=$(this).find('ul.th-fixed'); if(!el[0]){return false} setUlWidth(el,el.next()[0].offsetWidth!=el[0].offsetWidth); if(el.css('display')!=='block'){el.show();} el.css('top',top+'px'); }); var Timer; $(window).resize(function(){ Timer=setTimeout(function(){ if(Timer){clearTimeout(Timer)} $('ul.th-fixed').each(function(index,ele){ setUlWidth($(ele),true); }); },200); }); }) function setUlWidth(el,t){ if(!t){return false;} el.css('width',el.next()[0].offsetWidth+'px'); el.find('li').each(function(i,e){ var w=el.next().find('thead>tr>th'); $(e).css('width',w[i].offsetWidth+'px'); $(e).css('height',w[i].offsetHeight+'px'); }); }
手写测试版本:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="Generator" content="EditPlus®"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <title>Document</title> <style type="text/css"> #tbl{width:600px;height:300px;margin:0 auto;overflow:auto;position:relative} ul,li{margin:0;padding:0} table{width:100%;height:500px;border-collapse:collapse} table th,table td{ border:1px solid #ddd; text-align:center; } li{display:inline-block;list-style:none} .th-fixed li{ display: inline-block; border: 1px solid #ddd; border-left:none; border-right:none } .th-fixed{position:absolute;top:0;white-space: nowrap;} </style> </head> <body> <div id="tbl" class="scroll-tbl"> <ul class="th-fixed"><li>1</li><li>2</li><li>2</li><li>2</li><li>2</li></ul> <table> <thead> <tr> <th>1</th> <th>2</th> <th>3</th> <th>4</th> <th>5</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>1</td> <td>1</td> <td>1</td> <td>1</td> </tr> <tr> <td>1</td> <td>1</td> <td>1</td> <td>1</td> <td>1</td> </tr> <tr> <td>1</td> <td>1</td> <td>1</td> <td>1</td> <td>1</td> </tr> </tbody> </table> </div> <script type="text/javascript" src="js/jquery-1.11.3.js"></script> <script type="text/javascript"> $(function(){ //初始化的时候就给li设置宽度 $('ul.th-fixed').each(function(index,ele){ setUlWidth($(ele),true); }); //给目标div添加滚动事件 $('.scroll-tbl').on('scroll',function(){ //得到滚动的距离 var top=$(this)[0].scrollTop; var el=$(this).find('ul.th-fixed'); //如果没有ul就return if(!el[0]){return false} //setUlWidth(el,el.next()[0].offsetWidth!=el[0].offsetWidth); if(el.css('display')!=='block'){el.show();} //设置ul的top值用,因为滚动的时候ul也在动,要让他看起来不动 el.css('top',top+'px'); }); var Timer; $(window).resize(function(){ Timer=setTimeout(function(){ if(Timer){clearTimeout(Timer)} $('ul.th-fixed').each(function(index,ele){ setUlWidth($(ele),true); }); },200); }); }) function setUlWidth(el,t){ if(!t){return false;} el.css('width',el.next()[0].offsetWidth+'px'); el.find('li').each(function(i,e){ var w=el.next().find('thead>tr>th'); $(e).css('width',w[i].offsetWidth+'px'); $(e).css('height',w[i].offsetHeight+'px'); }); } </script> </body> </html>
方法扩展;
描述:当div滚动的时候判断是否有ul,ul不存在就构建,ul存在不构建,设置ul的top值就可以,让ul看起来静止不动,
js
$(function(){ //监听滚动条的事件 $('.scroll-div').on('scroll',function(){ //判断ul是否存在,不存在构建,存在不构建 var el=$(this).find('ul.h-fixed'); var ulWidth=$(this).find(".s-tbl")[0].offsetWidth+"px"; //var top=""; if(!el[0]){ var str="<ul class='h-fixed' style='width:"+ulWidth+"'>"; //构建ul //1.找到tabel里面的表头部分克隆出来 var $tr=$("#content-tbl tr:not([class])"); $tr.children().each(function(idx,ele){ if(idx==$tr.children().length-1){return} var wid=$(ele)[0].offsetWidth-1+"px";//因为有边距的原因,需要将边距的宽度减掉 var hei=$(ele)[0].offsetHeight-1+"px";//减去高度的border str+='<li style="width:'+wid+';height:'+hei+';line-height:'+hei+' ">'+$(ele).text()+'</li>'; }) str+="</ul>"; $(this).append(str); }else{ //就设置top值 el.css("top",$(this)[0].scrollTop+"px") } }); })
css
#tblForm .h-fixed{ position:absolute; top:0; font-size:14px; border-bottom:1px solid #A3C0E8; border-top:1px solid #A3C0E8; background-color:rgb(239, 239, 239) } #tblForm .h-fixed li{ display:inline-block; white-space: nowrap; text-align:center; line-height: 3.428571; border-right:1px solid #A3C0E8; } ul.h-fixed li:first-child{border-left: 1px solid #A3C0E8;background-color: #E2F0FF}
悬浮效果:
机场项目有一个问题,之前在做页面布局的时候,查询条件与展示的table没有分开,导致页面内容溢出或者浏览器缩放的时候,条件和表格一起滚动,出现不好的体验,需要修改的时候首先就的修改页面布局,这种工作量比较大,所以就想到一个方法,在页面滚动的时候,当表头不见的时候重新构建一个表头出来,固定在页面的上方,当原表头出现的时候,就将构建的表头隐藏或者销毁
js
$(function(){ $("body").css("position","relative"); $(document).on('scroll',function(e){ var el=$(this).find(".scrollTbl"); //判断距离是否ok if($(this).scrollTop()>115){//115px是因为表格上方有115的查询条件的高度 //判断是否有div if(!el[0]){ //构建div与table var $str=$('<div id="scrollTbl" style="position:absolute;top:0" class="scrollTbl"><table class="conTbl tbl"></table></div>');//表格的宽度需要自行设置,如果没有自适应的话 //1、获取到原始表格的表头 var str=""; $("#grid tr.tbl-header").each(function(idx,ele){ str="<tr>"; $(ele).children().each(function(i,e){ var _wid=$(e).css("width"); str+="<td style='width:"+_wid+"'>"+$(e).text()+"</td>" }) str+="</tr>"; }) $str.find("table").append($(str)); $("body").append($str); }else{ //设置top值 el.css("top",($(this).scrollTop()-15)+"px")//这里是因为body里面有15px的像素值 } }else{ el.remove(); } }); })
解释一下,为什么将事件绑定在document的上面呢,因为body上没有给定宽和高,就是说body是随着内容的高度变化的,所以将事件绑定在body上面是不生效的,要么就给body设置高度
以上的方法都只能解决表头单行的,如果要处理多行表格,或者是表格有合并,这种情况下还是不能用ul li的形式,最终只能转换成table的形式
思路:滚动的时候,判断时候有表头,没有就创建,找到现有的表格的表格进行遍历,创建每一个th,td,同时设置宽度(必须),因为表格的宽度是根据内容生成的,设置行合并列合并,当然如果是克隆就不需要,克隆的时候是将所有的属性一起克隆了
可能遇到的问题解决:
1、最好是将创建的表格与现有的表头放在同一个容器下,因为如果没有在同一个容器下面,当光标移动到表头的位置时,滚动表格就无效了,我现在的demo是没有,因为有样式冲突,
2、注意表格要加table-layout:fixed,原表格和现有表格都要加,不然会错位
遍历构建版本
js:
/*适应于报表类型一 reportForm1*/ $(function(){ $("#tblForm").on('scroll',function(){ //判断table是否存在,不存在就构建,反之 var el=$("body").find(".scrollTbl"); if(!el[0]){ var $str=$('<div class="scrollTbl" style="position:absolute;top:80px;overflow-y: scroll;"><table cellpadding="0" cellspacing="0" width="100%" style="table-layout:fixed;border-left: 1px solid #A3C0E8;border-bottom: 1px solid #A3C0E8;"></table></div>'); //1、获取到原始表格的表头 var str=""; $("#tblForm>table tr:has(th)").each(function(idx,ele){ str+="<tr>"; $(ele).children().each(function(i,e){ var _wid=$(e).width()+"px",_hei=$(e).height()+"px"; var col=$(e).attr("colspan"); var row=$(e).attr("rowspan"); col=!!col?col:1; row=!!row?row:1; str+="<th rowspan="+row+" colspan="+col+" style='width:"+_wid+";height:"+_hei+";padding:8px 10px;text-align:center;border-top:1px solid #A3C0E8;border-right:1px solid #A3C0E8;font-size:14px;background-color:#E2F0FF;font-weight:normal'>"+$(e).text()+"</th>" }) str+="</tr>"; }) $str.find("table").append($(str)); $("body").append($str); } }) })
克隆的版本
js
$(function(){ $("#bodyer").css("position","relative"); $("#bodyer").on('scroll',function(){ //判断是否有ul var el=$(this).find(".table-fixed"); if(!el[0]){ $(this).append("<div class='table-fixed' style='position:absolute;top:0'><table class='conTbl1'></table></div>"); var older=$(this).find(".conTbl1 thead tr").children(); //创建,找到表格的第一行进行克隆 var $thead=$(this).find(".conTbl1 thead").clone(); console.log($thead); $thead.find("tr").children().each(function(idx,ele){ $(ele).each(function(i,e){ $(e).css("width",older.eq(idx).css("width")); $(e).css("height",older.eq(idx).css("height")); }) }) $(this).find('.table-fixed table').append($thead); }else{ //就设置top值 el.css("top",$(this)[0].scrollTop+"px") } }) })
2018年7月16,这种方法最终还是出现了bug,因为不在同一个div中,bug就是在横向滚动的时候头部没有跟着动,另外一个鼠标放在头部的时候,无法滚动,所以还是必须放在同一个div中
更改好的代码:
/*适应于报表类型一 reportForm1*/ $(function(){ $("#tblForm").on('scroll',function(){ //判断table是否存在,不存在就构建,反之 var el=$("#tblForm").find(".scrollTbl"); if(!el[0]){ var $str=$('<div class="scrollTbl" style="position:absolute;top:0px;"><table cellpadding="0" cellspacing="0" style="table-layout:fixed;width:3000px;border-left: 1px solid #A3C0E8;border-bottom: 1px solid #A3C0E8;"></table></div>'); //1、获取到原始表格的表头 var str=""; $("#tblForm>table tr:has(th)").each(function(idx,ele){ str+="<tr>"; $(ele).children().each(function(i,e){ var _wid=$(e).width()+"px",_hei=$(e).height()+"px"; var col=$(e).attr("colspan"); var row=$(e).attr("rowspan"); col=!!col?col:1; row=!!row?row:1; str+="<th rowspan="+row+" colspan="+col+" style='width:"+_wid+";height:"+_hei+";padding:8px 10px;text-align:center;border-top:1px solid #A3C0E8;border-right:1px solid #A3C0E8;font-size:14px;background-color:#E2F0FF;font-weight:normal'>"+$(e).text()+"</th>" }) str+="</tr>"; }) $str.find("table").append($(str)); $("#tblForm").append($str); }else{ //设置top值 $("#tblForm").find(".scrollTbl").css("top",$(this)[0].scrollTop+"px") } }) })
2018年7月16号
一直在不断改进,上面的提出了还发现了一个bug,如果页面有自适应的话,表格的表体与表头还是会错位,所以就再需要加上一个方法,在页面resize的时候重新设置一个th的宽度,更新版本如下
/*适应于报表类型一 reportForm5*/ $(function(){ var Timer; $(window).resize(function(){ Timer=setTimeout(function(){ //setTblWidth($("#tblForm").find(".scrollTbl")) $("#tblForm").trigger("scroll");//上面的一种方法也可以 },200); }); $("#tblForm").on('scroll',function(){ //判断table是否存在,不存在就构建,反之 var el=$("#tblForm").find(".scrollTbl"); if(!el[0]){ var $str=$('<div class="scrollTbl" style="position:absolute;top:0px;width:100%"><table cellpadding="0" cellspacing="0" style="table-layout:fixed;border-left: 1px solid #A3C0E8;border-bottom: 1px solid #A3C0E8;"></table></div>'); //1、获取到原始表格的表头 var str=""; $("#tblForm>table tr:not([class])").each(function(idx,ele){//报表类型1和5的区别就在这个地方,因为表头不一样 str+="<tr>"; $(ele).children().each(function(i,e){ var _wid=$(e).width()+"px",_hei=$(e).height()+"px"; var col=$(e).attr("colspan"); var row=$(e).attr("rowspan"); col=!!col?col:1; row=!!row?row:1; str+="<th rowspan="+row+" colspan="+col+" style='width:"+_wid+";height:"+_hei+";padding:8px 10px;text-align:center;border-top:1px solid #A3C0E8;border-right:1px solid #A3C0E8;font-size:14px;background-color:#E2F0FF;font-weight:normal'>"+$(e).text()+"</th>" }) str+="</tr>"; }) $str.find("table").append($(str)); $("#tblForm").append($str); }else{ //设置top值 $("#tblForm").find(".scrollTbl").css("top",$(this)[0].scrollTop+"px") //设置宽度(,需要重新设置宽度) setTblWidth(el); } }) }) //设置宽度的方法 function setTblWidth(el,t){ if(!el)return; var w=el.prev().find('tr:not([class])');//动的 el.find('table tr').each(function(idx,ele){//固定的表头 $(ele).children().each(function(i,e){ $(e).css('width',$(w[idx]).children().eq(i).css("width")); $(e).css('height',$(w[idx]).children().eq(i).css("height")); }) }); }
还要注意 table-layout:fixed