DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> JavaScript教程:Table行定位效果
JavaScript教程:Table行定位效果
編輯:關於JavaScript     

網頁制作POLUOLUO文章簡介:要說明一下的是,我這個效果是用在一些普通的產品列表,當數據比較多時提高用戶體驗,而不是單單做數據顯示,跟excel那樣的方式是不同的。

近來有客戶要求用table顯示一大串數據,由於滾動後就看不到表頭,很不方便,所以想到這個效果。上次做 table排序 對table有了一些了解,這次更是深入了解了一番,發現table原來是這麼不簡單。

還不清楚這個效果叫什麼,有點像表頭固定的效果,就叫行定位吧,本來想把列定位也做出來,但暫時還沒這個需求,等以後有時間再弄吧。
在 淘寶的商品搜索頁 也看到類似的效果,但淘寶的不是table,而是li,而我這個是用在table上的。

要說明一下的是,我這個效果是用在一些普通的產品列表,當數據比較多時提高用戶體驗,而不是單單做數據顯示,跟excel那樣的方式是不同的。

效果預覽

為方便預覽,建議縮小浏覽器。

運行代碼框

[Ctrl+A 全部選擇 提示:你可先修改部分代碼,再按運行]

注意,使用ie8的兼容性視圖會有偏移。

程序原理

一開始的需求只是表頭部分在滾動時能一直固定在頭部,那關鍵要實現的就是讓tr能定位。首先想到的方法是給tr設置relative,用ie6/7測試以下代碼:

<!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">
<body>
<table cellpadding="5" cellspacing="0" border="1" width="100">
    <tr style="position:relative; left:100px;">
        <td>1</td>
        <td>2</td>
    </tr>
    <tr>
        <td>3</td>
        <td>4</td>
    </tr>
</table>
</body>
</html>

給tr設置relative後就能相對table定位了,看來很簡單啊,但問題是這個方法ie8和ff都無效,而且存在很多問題,所以很快就被拋棄了。
ps:該效果用來做tr的拖動會很方便。

接著想到的是給table插入一個新tr,克隆原來的tr,並設置這個tr為fixed(ie6為absolute),例如:

<!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">
<body>
<table cellpadding="5" cellspacing="0" border="1" width="100">
    <tr style="position:fixed; left:100px;">
        <td>1</td>
        <td>2</td>
    </tr>
    <tr style="position:absolute; left:200px;">
        <td>3</td>
        <td>4</td>
    </tr>
    <tr>
        <td>5</td>
        <td>6</td>
    </tr>
</table>
</body>
</html>

第一個問題是fixed的tr在ie7中不能進行定位,而且td在定位後並不能保持在表格中的布局,這樣在原表格插tr就沒意義了。
ps:fixed的相關應用可參考仿LightBox效果。

最後我用的方法是新建一個table,並把源tr克隆到新table中,然後通過對新table定位來實現效果。用這個方法關鍵有兩點,首先要做一個仿真度盡可能高的tr,還有是要准確的定位,這些請看後面的程序說明。

網頁制作POLUOLUO文章簡介:要說明一下的是,我這個效果是用在一些普通的產品列表,當數據比較多時提高用戶體驗,而不是單單做數據顯示,跟excel那樣的方式是不同的。

程序說明

【克隆table】

克隆一個元素用cloneNode就可以了,它有一個bool參數,表示克隆是否包含子節點。程序第一步就是克隆原table:

this._oTable = $(table);//源table
this._nTable = this._oTable.cloneNode(false);//新table
this._nTable.id = "";//避免id沖突

要注意雖然ie的cloneNode參數是可選的(默認是false),但在ff是必須的,建議使用時都寫上參數。
還要注意的是id屬性也會被克隆,也就是克隆後會有兩個相同id的元素(如果克隆對象有設置的話),這很容易會導致其他問題,程序會把克隆table的id屬性設空。
ps:table請用class來綁定樣式,用id的話新table就獲取不了樣式了。

克隆之後再設置樣式:

this._style.width = this._oTable.offsetWidth + "px";
this._style.position = isIE6 ? "absolute" : "fixed";
this._style.zIndex = 100;

一般來說offsetWidth是width+padding+border的結果,但table比較特別,測試下面的代碼:

<!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">
<body>
<table border="5" id="t" style="padding:10px; width:100px;">
    <tr>
        <td>1</td>
        <td>1</td>
    </tr>
</table>
<table width="100" id="t2" style="border:10px solid #000;">
    <tr>
        <td>1</td>
        <td>1</td>
    </tr>
</table>
<script>
alert(document.getElementById("t").offsetWidth);
alert(document.getElementById("t2").offsetWidth);
</script>
</body>
</html>

只要給table設置width(style或本身的width屬性),不管設置padding和border是多少,offsetWidth都等於width的值。
經測量offsetWidth是沒錯的,那就是說是table的width設置的問題。
w3c的table部分 中說width屬性是the desired width of the entire table,我估計entire就是包含了padding和border,找不到什麼其他說明,先這麼理解吧。
定位方面,除了不支持fixed的ie6用absolute,其他都使用fixed定位。

網頁制作POLUOLUO文章簡介:要說明一下的是,我這個效果是用在一些普通的產品列表,當數據比較多時提高用戶體驗,而不是單單做數據顯示,跟excel那樣的方式是不同的。

【克隆tr】

table有一個rows集合,包括了table的所有tr(包括thead和tfoot裡面的)。
程序的Clone方法會根據其參數克隆對應索引的tr:

this._index = Math.max(0, Math.min(this._oTable.rows.length - 1, isNaN(index) ? this._index : index));
this._oRow = this._oTable.rows[this._index];
var oT = this._oRow, nT = oT.cloneNode(true);

由於tr可能是包含在thead這些中,所以還要判斷一下:

if(oT.parentNode != this._oTable){
    nT = oT.parentNode.cloneNode(false).appendChild(nT).parentNode;
}

然後再插入到新table中:

if(this._nTable.firstChild){
    this._nTable.replaceChild(nT, this._nTable.firstChild);
}else{
    this._nTable.appendChild(nT);
}

因為程序允許修改克隆的tr,所以會判斷有沒有插入過,沒有就直接appendChild,否則用replaceChild替換原來的tr。

【table的border和frame屬性】

table的border屬性用來指定邊框寬度,table特有的frame屬性是用來設置或獲取表格周圍的邊框顯示的方式。

w3c的tabel的frame部分說明frame可以是以下值:

void: No sides. This is the default value.
above: The top side only.
below: The bottom side only.
hsides: The top and bottom sides only.
vsides: The right and left sides only.
lhs: The left-hand side only.
rhs: The right-hand side only.
box: All four sides.
border: All four sides.

這些值指明了要顯示的邊框。要留意的是雖然說void是默認值,但不設置的話其實是一個空值,這時四條邊框都會顯示。

還有frame對style設置的border沒有效果,測試下面代碼:

<!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">
<body>
<table width="100" border="5" frame="lhs">
    <tr>
        <td>1</td>
        <td>1</td>
    </tr>
</table>
<table width="100" style="border:5px solid #000;" border="10" frame="lhs">
    <tr>
        <td>1</td>
        <td>1</td>
    </tr>
</table>
</body>
</html>

這裡還可以看到如果同時設置table的border和style的border,那table的border就會失效。

程序中為了更美觀會自動去掉新table上面和下面的邊框,包括frame和style的:

if(this._oTable.border > 0){
    switch (this._oTable.frame) {
        case "above" :
        case "below" :
        case "hsides" :
            this._nTable.frame = "void"; break;
        case "" :
        case "border" :
        case "box" :
            this._nTable.frame = "vsides"; break;
    }
}
this._style.borderTopWidth = this._style.borderBottomWidth = 0;

其中空值在設置collapse之後會比較麻煩,在ie6/ie7中測試:

<!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">
<body>
<style type="text/css">
.t{width:100px; border-collapse:collapse;}
.t td{border:5px solid #999;}
</style>
<table class="t">
    <tr>
        <td>1</td>
        <td>1</td>
    </tr>
</table>
<br />
<table class="t" frame="vsides">
    <tr>
        <td>1</td>
        <td>1</td>
    </tr>
</table>
<br />
<table class="t" border="1">
    <tr>
        <td>1</td>
        <td>1</td>
    </tr>
</table>
<br />
<table class="t" border="1" frame="vsides">
    <tr>
        <td>1</td>
        <td>1</td>
    </tr>
</table>
</body>
</html>

後兩個的轉換還可以接受,所以在設置frame之前還是判斷一下border先。

網頁制作POLUOLUO文章簡介:要說明一下的是,我這個效果是用在一些普通的產品列表,當數據比較多時提高用戶體驗,而不是單單做數據顯示,跟excel那樣的方式是不同的。

【獲取背景色】

如果td是背景透明的話顯然不太美觀,最好是找一個合適的顏色來填充。
程序用的方法是,從當前td開始找,如果背景是透明的話,就再從父節點中找,直到找到有背景色為止。
一般來說透明的屬性值是"transparent",但在chrome裡卻是"rgba(0, 0, 0, 0)",所以用了一個屬性來保存透明值:

this._transparent = isChrome ? "rgba(0, 0, 0, 0)" : "transparent";

並在GetBgColor獲取背景色程序中使用:

while (bgc == this._transparent && (node = node.parentNode) != document) {
    bgc = CurrentStyle(node).backgroundColor;
}
return bgc == this._transparent ? "#fff" : bgc;

如果全部都是透明的話就會返回白色(#fff)。
這裡沒有考慮圖片背景的情況,畢竟圖片不一定會覆蓋整個背景。

【parentNode/offsetParent/parentElement】

上面用到了parentNode,這裡順便說說它跟offsetParent,parentElement的區別。
先看看parentNode在w3c的說明:
The parent of this node. All nodes, except Document, DocumentFragment, and Attr may have a parent. However, if a node has just been created and not yet added to the tree, or if it has been removed from the tree, this is null.
很簡單,就是節點的父節點,看過dom都知道。

再看看比較容易區分的offsetParent,它在mozilla和msdn都說得比較模糊,在w3c就比較清楚了:
The offsetParent attribute, when called on element A, must return the element determined by the following algorithm:
1,If any of the following holds true return null and stop this algorithm:
A is the root element.
A is the HTML body element.
The computed value of the position property for element A is fixed.
2,If A is an area HTML element which has a map HTML element somewhere in the ancestor chain return the nearest ancestor map HTML element and stop this algorithm.
3,Return the nearest ancestor element of A for which at least one of the following is true and stop this algorithm if such an ancestor is found:
The computed value of the position property is not static.
It is the HTML body element.
The computed value of the position property of A is static and the ancestor is one of the following HTML elements: td, th, or table.
4,Return null.

這裡主要有四點:

  1. 如果是根元素、body元素或元素的position是fixed,將返回null;
  2. 如果是area元素,會返回最接近的map元素;
  3. 返回至少符合以下一個條件的最接近該節點的元素:1,元素的position不是static;2,是body元素;3,源元素的position是static,祖先元素中的以下元素:td,th或table。
  4. 返回null。

其中第三點是最常見的情況,詳細可以看下面的測試:

<!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">
<body>
<table width="100" id="t">
    <tr>
        <td><div id="t1"></div></td>
        <td id="t2"><div style="position:absolute;">
                <div id="t3"></div>
            </div></td>
    </tr>
</table>
<div id="t4" style="position:fixed;"></div>
<script>
var $ = function (id) {
    return "string" == typeof id ? document.getElementById(id) : id;
};
alert($("t").offsetParent)//body
alert($("t1").offsetParent)//td
alert($("t2").offsetParent)//table
alert($("t3").offsetParent)//div
alert($("t4").offsetParent)//null
</script>
</body>
</html>

可見offsetParent跟parentNode的區別還是很大的。

而parentNode跟parentElement除了前者是w3c標准,後者只ie支持,其他的區別就不是那麼明顯了。
在ie中大部分情況下兩者的效果是一樣的,當然如果是一模一樣的話ie就沒必要弄這麼一個東西出來了,測試下面的代碼:

<!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">
<body>
<script>
var o = document.createDocumentFragment().appendChild(document.createElement("div"));
alert(o.parentNode)
alert(o.parentNode.nodeType)//11
alert(o.parentElement)//null
alert(document.body.parentNode)
alert(document.body.parentNode.nodeType)//1
alert(document.body.parentElement)//html
alert(document.body.parentNode.parentNode)
alert(document.body.parentNode.parentNode.nodeType)//9
alert(document.body.parentElement.parentElement)//null
</script>
</body>
</html>

可以看到當父節點的nodeType不是1,即不是element節點的話,它的parentElement就會是null。
這就明白了名字中“Element”的含義了。

網頁制作POLUOLUO文章簡介:要說明一下的是,我這個效果是用在一些普通的產品列表,當數據比較多時提高用戶體驗,而不是單單做數據顯示,跟excel那樣的方式是不同的。

【設置td寬度】

接下來就要設置td寬度了,要獲取某元素的寬度可以通過以下方法:

  1. 支持defaultView的可以直接用getComputedStyle獲取width。
  2. 獲取offsetWidth,再減去border和padding的寬度。
    這個本來也可以,但td的border寬度的獲取比較麻煩,下面有更方便的方法。
  3. 獲取clientWidth,再減去padding的寬度。
    這個跟方法2差不多,但更簡單方便。

注意ie的currentStyle不像getComputedStyle能獲取准確值,而只是一個設置值,像百分比、auto這些並不會自動轉成准確值,即使是得到准確值也不一定是實際值,像td即使設置一個很大的准確值,實際值也不會超過table本身的寬度。
所以在td這種比較特殊的結構中,除非是很理想的狀況,否則用currentStyle基本沒戲,而且在這個效果中即使是差了1px也會看不舒服。
對於支持defaultView的當然可以直接獲取,否則就用上面的方法3來獲取:

style.width = (document.defaultView ? parseFloat(css.width)
    : (o.clientWidth - parseInt(css.paddingLeft) - parseInt(css.paddingRight))) + "px";

但這裡不管哪個方法都有一個問題,就是出現scroll的情況,不過還好td這個元素即使設置了overflow為scroll也不會出現滾動條,除了ie8和chrome。
程序沒對這個情況做處理,畢竟給td設scroll也不常見,而且支持這個的浏覽器不多,沒必要花太多時間在這裡。
ps:關於td寬度的自動調整可以參考w3c的table-layout部分。

如果有影響原td結構的設置,例如colspan之類的就要留意,錯誤的結構很可能導致一些異常變形。
如果對原表格結構或內容做了修改,應該執行一次Clone方法重構新table。
本部分對體驗比較重要,如果設置不當就會有變形的感覺,很不美觀。

【borderCollapse】

上面說到td的border寬度的獲取比較麻煩,那到底有多煩呢?
如果只是一般情況的話,通過borderLeftWidth和borderRightWidth獲取寬度就可以了。
ps:如果borderStyle是"none"的話,那麼border就會沒效,所以如果要取border寬度的話最好先判斷一下borderStyle是不是"none"。

但table有一個特別的樣式borderCollapse,設置table的邊框模型。
它有兩個值,分別是separate(分開,默認值)和collapse(合並)。
separate就是我們一般看到的效果,這裡主要討論collapse,先看mozilla怎麼說的:
In the collapsed border model, adjacent table cells share borders.
意思是在collapse border模型中,相鄰的td會共用邊框。看下面的例子會更明白:

<!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">
<body>
<style type="text/css">
.t{line-height:40px;width:200px; }
.t td{border:5px solid #999;}
</style>
<table class="t" style="border-collapse:collapse;">
  <tr>
    <td width="50">&nbsp;</td>
    <td style="border-left-width:10px; border-left-style:dotted;">&nbsp;</td>
    <td width="50">&nbsp;</td>
  </tr>
</table>
<table class="t">
  <tr>
    <td width="50">&nbsp;</td>
    <td style="border-left-width:10px; border-left-style:dotted;">&nbsp;</td>
    <td width="50">&nbsp;</td>
  </tr>
</table>
</body>
</html>

可以看到使用collapse之後,相鄰td的邊框都合並成一條而且是以相鄰邊框中寬度較大的那條為准。
那td跟table之間呢,參考下面的例子:

<!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">
<body>
<style type="text/css">
.t{line-height:40px;width:200px;border-collapse:collapse;}
.t td{border:5px solid #999;}
</style>
<table class="t" id="t1">
  <tr>
    <td width="50" style="border-left:10px dotted #999;">&nbsp;</td>
    <td>&nbsp;</td>
    <td width="50">&nbsp;</td>
  </tr>
</table>
<br />
<table class="t" id="t2" style="border-left:10px dotted #999;">
  <tr>
    <td width="50">&nbsp;</td>
    <td>&nbsp;</td>
    <td width="50">&nbsp;</td>
  </tr>
</table>
</body>
</html>

可見table和td之間也是遵從同樣規則。
還有的是當設置了collapse那cellspacing就無效了。順便說說border-spacing,它其實就是cellspacing在css中的樣式形式,只是ie在ie8才開始支持,詳細可以看 mozilla的說明。

collapse的一個常見應用是做邊框表格,例如1px邊框的表格:

<!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">
<body>
<style type="text/css">
.t{line-height:40px;width:200px;}
.t1{border-collapse:collapse;}
.t1 td{border:1px solid #999;}
.t2{background-color:#999;}
.t2 td{background-color:#FFF;}
</style>
<table class="t t1">
  <tr>
    <td width="50">&nbsp;</td>
    <td>&nbsp;</td>
    <td width="50">&nbsp;</td>
  </tr>
</table>
<table class="t t2" cellspacing="1">
  <tr>
    <td width="50">&nbsp;</td>
    <td>&nbsp;</td>
    <td width="50">&nbsp;</td>
  </tr>
</table>
</body>
</html>

前者用的collapse,後者是用table背景色模擬,雖然效果都一樣,但前者顯然較好,才是真正的“邊框”。

在使用了collapse之後,要寫一個通用的獲取邊框寬度程序會變得十分麻煩,而且有些情況下甚至沒辦法判斷獲取。
詳細情況這裡就不細說了,有興趣研究的話可以看看w3c的The collapsing border model,當然要想全部了解的話還要在各個浏覽器中研究。

網頁制作POLUOLUO文章簡介:要說明一下的是,我這個效果是用在一些普通的產品列表,當數據比較多時提高用戶體驗,而不是單單做數據顯示,跟excel那樣的方式是不同的。

【元素位置】

table的樣式設置好後,還需要獲取原table和原tr的位置參數,為後面的元素定位做准備。
要獲取某個元素相對文檔的位置,傳統的做法是獲取對象的offsetLeft/offsetTop,然後不斷獲取offsetParent的offsetLeft/offsetTop,直到找不到offsetParent為止。
得到的結果就是相對文檔的位置了,上面已經介紹過offsetParent,原理應該都明白了吧。

不過這裡介紹一個更好的方法,通過getBoundingClientRect方法來獲取。
在mozilla是這麼說明的:
The returned value is a TextRectangle object, which contains read-only left, top, right and bottom properties describing the border-box, in pixels, with the top-left relative to the top-left of the viewport...
返回一個TextRectangle對象,包含left, top, right和bottom幾個只讀屬性,以px為單位來表示邊界框相對視窗左上角的位置。(偶英文爛啊)
注意是相對視窗,不是文檔哦,如果要相對文檔還必須加上scrollLeft/scrollTop。
通過下面的測試可以看到兩個方法返回的結果都是相同的:

<!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">
<body>
<style type="text/css">
.t{line-height:40px;width:200px; border:10px solid; margin-top:900px; margin-left:500px;}
</style>
<div class="t" id="t"></div>
<script>
var o = document.getElementById("t");
var rect = o.getBoundingClientRect();
var iLeft1 = rect.left +  document.documentElement.scrollLeft, iTop1 = rect.top +  document.documentElement.scrollTop;
var iLeft2 = o.offsetLeft, iTop2 = o.offsetTop;
while (o.offsetParent) { o = o.offsetParent; iLeft2 += o.offsetLeft; iTop2 += o.offsetTop; }
alert(iLeft1+"_"+iLeft2)
alert(iTop1+"_"+iTop2)
</script>
</body>
</html>

程序中就是用getBoundingClientRect來獲取位置參數:

//用getBoundingClientRect獲取原table位置
var top = this._doc.scrollTop, rect = this._oTable.getBoundingClientRect();
this._oTableLeft = rect.left + this._doc.scrollLeft;
this._oTableTop = rect.top + top;
this._oTableBottom = rect.bottom + top;
//獲取原tr位置
rect = this._oRow.getBoundingClientRect();
this._oRowTop = rect.top + top;
this._oRowBottom = rect.bottom + top;

顯然用getBoundingClientRect更方便快捷。
這個方法雖然是ie的產物,但已經是w3c的標准,而且ff3,Opera和最新版的chrome都已經支持了這個方法,可以放心使用。
這裡只是簡單介紹,想了解更多可以看w3c的View Module部分。

獲取原table和tr的位置後,還需要計算新table的位置。
程序可以自定義新table位於視窗位置的百分比,例如頂部是0,中間是0.5,底部是1,可以在程序初始化時或用SetPos方法來設置。
這裡主要獲取視窗高度和新table在視窗的top值:

this._viewHeight = document.documentElement.clientHeight;
this._ntViewTop = (this._viewHeight - this._nTableHeight) * this._pos;

定位范圍實際上是從視框頂部到視框高度減去新table高度的范圍內的,所以計算時要先把視窗高度減去新table的高度。

網頁制作POLUOLUO文章簡介:要說明一下的是,我這個效果是用在一些普通的產品列表,當數據比較多時提高用戶體驗,而不是單單做數據顯示,跟excel那樣的方式是不同的。

【元素定位】

萬事俱備,只欠定位了。
由於要根據窗口滾動狀態來判斷計算定位,scrollTop/scrollLeft的獲取必不可少。
但在chrome中就算用了DOCTYPE,也要用document.body來獲取scrollTop/scrollLeft,盡管它確實有document.documentElement。
對chrome了解不多,也不知哪裡能查它的相關文檔,程序裡就直接做個判斷算了:

this._doc = isChrome ? document.body : document.documentElement;

定位的第一步就是判斷是否需要定位,這裡的判斷標准有兩個,第一個是原tr是否超過了視窗范圍,還有是新table要顯示的位置是否在原table的顯示范圍內。
第一點可以通過原tr位置的頂部和底部是否超過視窗的頂部和底部來判斷:

var top = this._doc.scrollTop, left = this._doc.scrollLeft
    ,outViewTop = this._oRowTop < top, outViewBottom = this._oRowBottom > top + this._viewHeight;
if(outViewTop || outViewBottom){}

在看第二點之前先看看程序中的Auto屬性,它是用來指定否自動定位的。
如果自動定位的話當原tr離開視框頂部新table就會定位到視框頂部,原tr離開底部新table就會定位到視框底部,這樣看上去會比較自然順暢。
如果不選擇自動的話就會根據SetPos方法中計算得到的新table視窗top值來設置定位:

var viewTop = !this.Auto ? this._nTableViewTop
    : (outViewTop ? 0 : (this._viewHeight - this._nTableHeight))//視窗top
    ,posTop = viewTop + top;//位置top

接著就判斷新table要顯示的位置是否在原table的顯示范圍內,這個可以通過新table位置的頂部和底部是否超過原table的頂部和底部來判斷:

if(posTop > this._oTableTop && posTop + this._nTableHeight < this._oTableBottom){}

當符合所有的條件就可以進行定位了,如果是fixed定位的就使用相對視窗的top值:

this._style.top = viewTop + "px";
this._style.left = this._oTableLeft - left + "px";

像ie6是absolute定位的就要使用相對文檔的top值:

this._style.top = posTop + "px";
this._style.left = this._oTableLeft + "px";

考慮到左右滾動的情況,left也必須設置。

當然不符合條件就會隱藏新table,程序中給top設置一個很大的負值來間接“隱藏”它。
用負值是因為這樣不會把ie6的頁面拉長,不用display是因為上面需要獲取它的offsetHeight,如果用display隱藏就獲取不了啦。

最後把Run程序綁定到window的scroll事件中就可以了,而window在resize時視框高度會發生變化,所以resize事件要綁定SetPos程序。

網頁制作POLUOLUO文章簡介:要說明一下的是,我這個效果是用在一些普通的產品列表,當數據比較多時提高用戶體驗,而不是單單做數據顯示,跟excel那樣的方式是不同的。

【覆蓋select】

只要用到了定位,就不得不面對一個老對手“ie6的select”。
我在之前的文章也介紹過一些解決方法(參考這裡的覆蓋select),這裡不能直接隱藏select,那看來只能用iframe了。
但用iframe有一個很大的問題,在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">
<body>
<style type="text/css">
body{height:1000px;}
.t{height:300px;width:200px; border:1px solid; position:absolute; background:#FFF;top:0;left:0;}
</style>
<iframe class="t" id="t"></iframe>
<select></select><br />
<select></select><br />
<select></select><br />
<select></select><br />
<select></select><br />
<select></select><br />
<select></select><br />
<select></select><br />
<select></select><br />
<select></select><br />
</body>
</html>

可以看到,即使是iframe,在拖動滾動條的時候,select仍然在後面閃啊閃,在本程序中這個現象會尤其明顯。
看來還得用隱藏select的方法,最好的做法是只隱藏在新table後面的select,而不影響其他select的正常顯示。
那關鍵就是如何判斷select是否在新table後面,這個可以通過位置坐標判斷,剛好可以用到上面的getBoundingClientRect。
一般的思路是判斷新table和select的坐標,根據位置判斷select的顯示和隱藏。
但如果有多個實例,可能會導致select在一個實例中要隱藏,卻在另一個要顯示的情況。

為了解決沖突,程序給select加了一個_count屬性作為計數器,用來記錄有多少實例把該select隱藏了。
如果當前實例判斷該select要隱藏,就給其_count加1,隱藏後存放到實例的_selects集合中。
在恢復顯示_selects中的select時,先給select的_count減1,如果得到的_count是0,那說明沒有其他實例要隱藏它,就可以設置顯示了,最後清空_selects集合。
在判斷是否隱藏select前還必須恢復一次該實例_selects裡面的select,否則就會造成_count只加不減的情況。

程序中的SetSelect方法就是用來判斷和設置select的:

this.ResetSelect();
var rect = this._nTable.getBoundingClientRect();
//把需要隱藏的放到_selects集合
this._selects = Filter(this._oTable.getElementsByTagName("select"), Bind(this, function(o){
    var r = o.getBoundingClientRect();
    if(r.top <= rect.bottom && r.bottom >= rect.top){
        o._count ? o._count++ : (o._count = 1);//防止多個實例沖突
        //設置隱藏
        var visi = o.style.visibility;
        if(visi != "hidden"){ o._css = visi; o.style.visibility = "hidden"; }
       
        return true;
    }
}))

其中ResetSelect方法是用來恢復顯示select的:

forEach(this._selects, function(o){ !--o._count && (o.style.visibility = o._css); });
this._selects = [];

但這個方法在快速滾屏時還是無能為力,而且select越多效率也隨之下降,各位有更好方法的話歡迎交流。

【Chrome一個bug】

在測試的時候發現Chrome一個bug,測試下面代碼:

<!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">
<body>
<table border="1">
    <tr>
        <td id="tt"></td>
    </tr>
</table>
<div id="t"></div>
<script>
document.getElementById("t").offsetWidth;
document.getElementById("tt").innerHTML = "<select><option>test</option></select>";
</script>
</body>
</html>

一個毫不相干的操作居然令table沒有自動撐開,加上前面的問題,看來Chrome的路還很長啊。

使用說明

實例化一個TableFixed對象只需要一個參數table的id:

new TableFixed("idTable");

實例化時有4個可選屬性:
Index: 0,//tr索引
Auto: true,//是否自動定位
Pos: 0,//自定義定位位置百分比(0到1)
Hide: false//是否隱藏(不顯示)

其中Index和Pos在實例化之後就不能使用。
要修改克隆行可以用Clone方法,其參數是要克隆tr的索引。
要修改自定義定位位置可以用SetPos方法,其參數是要定位的位置百分比。

具體使用請參考實例。

程序源碼

代碼拷貝框

[Ctrl+A 全部選擇 然後拷貝]

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved