【元素定位】
萬事俱備,只欠定位了。
由于要根據(jù)窗口滾動狀態(tài)來判斷計算定位,scrollTop/scrollLeft的獲取必不可少。
但在chrome中就算用了DOCTYPE,也要用document.body來獲取scrollTop/scrollLeft,盡管它確實有document.documentElement。
對chrome了解不多,也不知哪里能查它的相關(guān)文檔,程序里就直接做個判斷算了:
this._doc = isChrome ? document.body : document.documentElement;
定位的第一步就是判斷是否需要定位,這里的判斷標(biāo)準(zhǔn)有兩個,第一個是原tr是否超過了視窗范圍,還有是新table要顯示的位置是否在原table的顯示范圍內(nèi)。
第一點可以通過原tr位置的頂部和底部是否超過視窗的頂部和底部來判斷:
var top = this._doc.scrollTop, left = this._doc.scrollLeft
,outViewTop = this._oRowTop < top, outViewBottom = this._oRowBottom > top + this._viewHeight;
if(outViewTop || outViewBottom){
}
在看第二點之前先看看程序中的Auto屬性,它是用來指定否自動定位的。
如果自動定位的話當(dāng)原tr離開視框頂部新table就會定位到視框頂部,原tr離開底部新table就會定位到視框底部,這樣看上去會比較自然順暢。
如果不選擇自動的話就會根據(jù)SetPos方法中計算得到的新table視窗top值來設(shè)置定位:
var viewTop = !this.Auto ? this._nTableViewTop
: (outViewTop ? 0 : (this._viewHeight - this._nTableHeight))//視窗top
,posTop = viewTop + top;//位置top
接著就判斷新table要顯示的位置是否在原table的顯示范圍內(nèi),這個可以通過新table位置的頂部和底部是否超過原table的頂部和底部來判斷:
if(posTop > this._oTableTop && posTop + this._nTableHeight < this._oTableBottom){
}
當(dāng)符合所有的條件就可以進(jìn)行定位了,如果是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也必須設(shè)置。
當(dāng)然不符合條件就會隱藏新table,程序中給top設(shè)置一個很大的負(fù)值來間接“隱藏”它。
用負(fù)值是因為這樣不會把ie6的頁面拉長,不用display是因為上面需要獲取它的offsetHeight,如果用display隱藏就獲取不了啦。
最后把Run程序綁定到window的scroll事件中就可以了,而window在resize時視框高度會發(fā)生變化,所以resize事件要綁定SetPos程序。
【覆蓋select】
只要用到了定位,就不得不面對一個老對手“ie6的select”。
我在之前的文章也介紹過一些解決方法(參考這里的覆蓋select),這里不能直接隱藏select,那看來只能用iframe了。
但用iframe有一個很大的問題,在ie6測試下面的代碼,并拖動滾動條:

Code
<!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仍然在后面閃啊閃,在本程序中這個現(xiàn)象會尤其明顯。
看來還得用隱藏select的方法,最好的做法是只隱藏在新table后面的select,而不影響其他select的正常顯示。
那關(guān)鍵就是如何判斷select是否在新table后面,這個可以通過位置坐標(biāo)判斷,剛好可以用到上面的getBoundingClientRect。
一般的思路是判斷新table和select的坐標(biāo),根據(jù)位置判斷select的顯示和隱藏。
但如果有多個實例,可能會導(dǎo)致select在一個實例中要隱藏,卻在另一個要顯示的情況。
為了解決沖突,程序給select加了一個_count屬性作為計數(shù)器,用來記錄有多少實例把該select隱藏了。
如果當(dāng)前實例判斷該select要隱藏,就給其_count加1,隱藏后存放到實例的_selects集合中。
在恢復(fù)顯示_selects中的select時,先給select的_count減1,如果得到的_count是0,那說明沒有其他實例要隱藏它,就可以設(shè)置顯示了,最后清空_selects集合。
在判斷是否隱藏select前還必須恢復(fù)一次該實例_selects里面的select,否則就會造成_count只加不減的情況。
程序中的SetSelect方法就是用來判斷和設(shè)置select的:

Code
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);//防止多個實例沖突
//設(shè)置隱藏
var visi = o.style.visibility;
if(visi != "hidden"){ o._css = visi; o.style.visibility = "hidden"; }
return true;
}
}))
其中ResetSelect方法是用來恢復(fù)顯示select的:
forEach(this._selects, function(o){ !--o._count && (o.style.visibility = o._css); });
this._selects = [];
但這個方法在快速滾屏?xí)r還是無能為力,而且select越多效率也隨之下降,各位有更好方法的話歡迎交流。
【Chrome一個bug】
在測試的時候發(fā)現(xiàn)Chrome一個bug,測試下面代碼:

Code
<!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對象只需要一個參數(shù)table的id:
new TableFixed("idTable");
實例化時有4個可選屬性:
Index: 0,//tr索引
Auto: true,//是否自動定位
Pos: 0,//自定義定位位置百分比(0到1)
Hide: false//是否隱藏(不顯示)
其中Index和Pos在實例化之后就不能使用。
要修改克隆行可以用Clone方法,其參數(shù)是要克隆tr的索引。
要修改自定義定位位置可以用SetPos方法,其參數(shù)是要定位的位置百分比。
具體使用請參考實例。
程序源碼

Code
var TableFixed = function(table, options){
this._oTable = $(table);//原table
this._nTable = this._oTable.cloneNode(false);//新table
this._nTable.id = "";//避免id沖突
this._oTableLeft = this._oTableTop = this._oTableBottom = 0;//記錄原table坐標(biāo)參數(shù)
this._oRowTop = this._oRowBottom = 0;//記錄原tr坐標(biāo)參數(shù)
this._viewHeight = this._oTableHeight = this._nTableHeight = 0;//記錄高度
this._nTableViewTop = 0;//記錄新table視框top
this._selects = [];//select集合,用于ie6覆蓋select
this._style = this._nTable.style;//用于簡化代碼
//chrome的scroll用document.body
this._doc = isChrome ? document.body : document.documentElement;
//chrome透明用rgba(0, 0, 0, 0)
this._transparent = isChrome ? "rgba(0, 0, 0, 0)" : "transparent";
this.SetOptions(options);
this._index = this.options.Index;
this._pos = this.options.Pos;
this.Auto = !!this.options.Auto;
this.Hide = !!this.options.Hide;
addEventHandler(window, "resize", Bind(this, this.SetPos));
addEventHandler(window, "scroll", Bind(this, this.Run));
this._oTable.parentNode.insertBefore(this._nTable, this._oTable);
this.Clone();
};
TableFixed.prototype = {
//設(shè)置默認(rèn)屬性
SetOptions: function(options) {
this.options = {//默認(rèn)值
Index: 0,//tr索引
Auto: true,//是否自動定位
Pos: 0,//自定義定位位置百分比(0到1)
Hide: false//是否隱藏(不顯示)
};
Extend(this.options, options || {});
},
//克隆表格
Clone: function(index) {
//設(shè)置table樣式
this._style.width = this._oTable.offsetWidth + "px";
this._style.position = isIE6 ? "absolute" : "fixed";
this._style.zIndex = 100;
//設(shè)置index
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);
if(oT.parentNode != this._oTable){
nT = oT.parentNode.cloneNode(false).appendChild(nT).parentNode;
}
//插入新行
if(this._nTable.firstChild){
this._nTable.replaceChild(nT, this._nTable.firstChild);
}else{
this._nTable.appendChild(nT);
}
//去掉table上面和下面的邊框
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;
//設(shè)置td樣式
var nTds = this._nTable.rows[0].cells;
forEach(this._oRow.cells, Bind(this, function(o, i){
var css = CurrentStyle(o), style = nTds[i].style;
//設(shè)置td背景
style.backgroundColor = this.GetBgColor(o, css.backgroundColor);
//設(shè)置td的width,沒考慮ie8/chrome設(shè)scroll的情況
style.width = (document.defaultView ? parseFloat(css.width)
: (o.clientWidth - parseInt(css.paddingLeft) - parseInt(css.paddingRight))) + "px";
}));
//獲取table高度
this._oTableHeight = this._oTable.offsetHeight;
this._nTableHeight = this._nTable.offsetHeight;
this.SetRect();
this.SetPos();
},
//獲取背景色
GetBgColor: function(node, bgc) {
//不要透明背景(沒考慮圖片背景)
while (bgc == this._transparent && (node = node.parentNode) != document) {
bgc = CurrentStyle(node).backgroundColor;
}
return bgc == this._transparent ? "#fff" : bgc;
},
//設(shè)置坐標(biāo)屬性
SetRect: function() {
if(this._oTable.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;
}else{//chrome不支持getBoundingClientRect
//獲取原table位置
var o = this._oTable, iLeft = o.offsetLeft, iTop = o.offsetTop;
while (o.offsetParent) { o = o.offsetParent; iLeft += o.offsetLeft; iTop += o.offsetTop; }
this._oTableLeft = iLeft;
this._oTableTop = iTop;
this._oTableBottom = iTop + this._oTableHeight;
//獲取原tr位置
o = this._oRow; iTop = o.offsetTop;
while (o.offsetParent) { o = o.offsetParent; iTop += o.offsetTop; }
this._oRowTop = iTop;
this._oRowBottom = iTop + this._oRow.offsetHeight;
}
},
//設(shè)置新table位置屬性
SetPos: function(pos) {
//設(shè)置pos
this._pos = Math.max(0, Math.min(1, isNaN(pos) ? this._pos : pos));
//獲取位置
this._viewHeight = document.documentElement.clientHeight;
this._nTableViewTop = (this._viewHeight - this._nTableHeight) * this._pos;
this.Run();
},
//運(yùn)行
Run: function() {
if(!this.Hide){
var top = this._doc.scrollTop, left = this._doc.scrollLeft
//原tr是否超過頂部和底部
,outViewTop = this._oRowTop < top, outViewBottom = this._oRowBottom > top + this._viewHeight;
//原tr超過視窗范圍
if(outViewTop || outViewBottom){
var viewTop = !this.Auto ? this._nTableViewTop
: (outViewTop ? 0 : (this._viewHeight - this._nTableHeight))//視窗top
,posTop = viewTop + top;//位置top
//在原table范圍內(nèi)
if(posTop > this._oTableTop && posTop + this._nTableHeight < this._oTableBottom){
//定位
if(isIE6){
this._style.top = posTop + "px";
this._style.left = this._oTableLeft + "px";
setTimeout(Bind(this, this.SetSelect), 0);//iebug
}else{
this._style.top = viewTop + "px";
this._style.left = this._oTableLeft - left + "px";
}
return;
}
}
}
//隱藏
this._style.top = "-99999px";
isIE6 && this.ResetSelect();
},
//設(shè)置select集合
SetSelect: function() {
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);//防止多個實例沖突
//設(shè)置隱藏
var visi = o.style.visibility;
if(visi != "hidden"){ o._css = visi; o.style.visibility = "hidden"; }
return true;
}
}))
},
//恢復(fù)select樣式
ResetSelect: function() {
forEach(this._selects, function(o){ !--o._count && (o.style.visibility = o._css); });
this._selects = [];
}
};
下載完成測試代碼
標(biāo)簽:
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:博客園