前幾天項目經理跟我說有這么個需求:一個很大的表格,垂直滑動時表頭固定,水平滑動里第一、二列固定。我一想這也太難實現了,直接回復說可能實現不了,可是沒過會經理發個12306網站的一個現存的實現樣子,汗!我只能說那我先調研下實現的成本吧。心里想想反正客戶給錢,沒有什么理由不做啊。
1. 前期調研
先打開12306網站上面的那個表格,打開瀏覽器調試工具,發現他們好像用的是插件(dhtmlxgrid),再看了下布局,發現它們把固定的表頭和列放在一個DIV里面,心里想這樣的需求肯定很常見,說不定網上早有相應插件了,於是上google搜“table header fixed ”(個人比較喜歡用英文關鍵字搜索信息),第一條信息就是一個基於jquery的插件叫Fixed Header Table,而且還不要錢的,於是下載下來,研究它的源碼,發現它的布局與12306上面的類似也是把固定的表頭和列放在一個DIV,然后在此DIV里面放table,整體布局如下圖所示:
通過控制單元格的寬度和高度,使整體的單元格對齊,最后監聽maindiv的滑動事件,當它滑動時通過js去滑動headerdiv與columndiv的滑動。其實里面最麻煩的應該是單元格對齊問題,因為單元格寬和高會隨里面的內容變化(如果你想自適應的話)。
知道實現原理后,然后對照下我實際的項目,我項目中的表格和12306中的表格有的類似,不是普通的數據表格,里面的單元格的合並,而且我項目中的表格還有分組(grounp),截個圖:
里面有編輯功能,還得在行和列分別加個total,表格夠復雜吧。Fixed Header Table這個插件不支持我的表格(單元格的合並),dhtmlxgrid插件很強大,但要錢,項目中本來就有jquery了,不想再引用其它框架了,自己動手,豐衣足食!
2. 具體實現
2.1 簡化需求
以前的布局就是一個table,要做這種行列鎖定的表格,布局肯定大改,為了簡單點,我把我表格中的單元格寬度和高度固定,超出的文字用…表示,呵呵,我還是想怎么簡單怎么來,這么做客戶也接受。把單元格定死后,代碼量會少很多。布局盡量用CSS去控制,JS只控制滑動和初始化區域大小(因為我頁面是自適應的)。
2.2 html+CSS
先畫個兩行兩列的表格,把整體布局定好,我做布局都先做整體,再做局部。先看下整體布局的html:
<table>
<thead>
<tr >
<th></th>
<th>
<div id="Headerdiv" style=" overflow: hidden"></div>
</th>
</tr>
</thead>
<tbody>
<tr>
<td >
<div id="Columndiv" style=" overflow: hidden"></div>
</td>
<td>
<div id="maindiv" style=" overflow: scroll" onscroll="fnScroll()"></div>
</td>
</tr>
</tbody>
</table>
里面div的主要屬性和事件都已經聲明,接下來做thead里的兩行固定列,第一個th的源代碼:
<th id="firstTd " class="tdborder">
<table cellspacing="0" cellpadding="0">
<tr>
<td class="tableFirstCol txtcenter td_right td_bottom">Project</td>
<td class="tableSecondCol td_bottom">
<table cellspacing="0" cellpadding="0" class="Tamount">
<tr>
<td colspan="3" class="txtcenter td_bottom">Total</td>
</tr>
<tr>
<td class="current td_right">current</td>
<td class="scenario td_right">scenario</td>
<td class="different">different</td>
</tr>
</table>
</td>
</tr>
<tr>
<td class="tableFirstCol td_right">Total</td>
<td class="tableSecondCol ">
<table cellpadding="0" cellspacing="0">
<tr>
<td class="current td_right"><input type="text" /></td>
<td class="scenario td_right"><input type="text" /</td>
<td class="different"><input type="text" /</td>
</tr>
</table>
</td>
</tr>
</table>
</th>
然后是第二個th里面的Headerdiv的源代碼:
<div id="Headerdiv" >
<table cellspacing="0" cellpadding="0" width="1560" id="headertable" class="td_right" >
<tr >
<td class="td_left">
<table cellspacing="0" cellpadding="0" class="Tamount">
<tr>
<td colspan="3" class="txtcenter td_bottom">1999</td>
</tr>
<tr>
<td class="current td_right">current</td>
<td class="scenario td_right">scenario</td>
<td class="different">different</td>
</tr>
</table>
</td>
重復TD...
</tr>
<tr >
<td class="td_left td_top">
<table cellpadding="0" cellspacing="0">
<tr>
<td class="current td_right"><input type="text" /></td>
<td class="scenario td_right"><input type="text" /</td>
<td class="different"><input type="text" /</td>
</tr>
</table>
</td>
重復TD...
</tr>
</table>
</div>
大家可能注意到里面對套了這么多table有異議,本人是為了站單元格對齊才出此下策,在整個頁面的制作過程中單元格的邊框對齊是最煩人的。
接下來展示固定列的html源碼:
<div id="Columndiv" class="fixedcol">
<table cellspacing="0" cellpadding="0" >
<tr>
<td colspan="2" class="tdgroup">
group name group name group name group nam name group name group name
</td>
</tr>
<tr>
<td class="tableFirstCol">Project Name 1 </td>
<td class="tableSecondCol">
<table cellpadding="0" cellspacing="0">
<tr>
<td class="current td_right"><input type="text" /></td>
<td class="scenario td_right"><input type="text" /</td>
<td class="different"><input type="text" /</td>
</tr>
</table>
</td>
</tr>
里面class為tdgrounp的為表格的分組名,這分組名如果太長就會換行,我會在JS里面控制maindiv里對應單元格的高度,也是JS代碼里唯一控制單元格的代碼。
maindiv里的HTML就不粘出來了,里就就放了一個table。
3.3 javascript
接下來講講JS,JS 還是很簡單的(基於jquery):
$(document).ready(function () {
fnAdjustTable();
//先求頁面給定的高度
var _h = $("#tablediv").height();
var _w = $("#tablediv").width();
//然后設定相關div的高度
var _head_h = $("#thead").height();
$("#maindiv").height(_h - _head_h);
$("#Columndiv").height(_h - _head_h - 18);//18是空出了相應滾動條的距離
var _clo_w = $("#Columndiv").width();
$("#Headerdiv").width(_w - _clo_w-18 );
$("#maindiv").width(_w - _clo_w);
});
function fnAdjustTable() {
//調整組名的單元格高度
$('#Columndiv .tdgroup').each(function (i) {
//不同瀏覽器這高度可能不一樣,相關一兩個像素
if ($.browser.msie) {
$("#maindiv .tdgroup:eq(" + i + ")").height($(this).height());
} else {
$("#maindiv .tdgroup:eq(" + i + ")").height($(this).height() + 1);
}
});
}
//滑動事件
function fnScroll () {
$('#Headerdiv').scrollLeft($('#maindiv').scrollLeft());
$('#Columndiv').scrollTop($('#maindiv').scrollTop());
}


