一区二区久久-一区二区三区www-一区二区三区久久-一区二区三区久久精品-麻豆国产一区二区在线观看-麻豆国产视频

JavaScript Table行定位效果

上次做table排序?qū)able有了一些了解,這次更是深入了解了一番,發(fā)現(xiàn)table原來是這么不簡單。
還不清楚這個效果叫什么,就叫行定位吧,本來想把列定位也做出來,但暫時還沒這個需求,等以后有時間再弄吧。
程序原理
一開始的需求只是表頭部分在滾動時能一直固定在頭部,那關(guān)鍵要實現(xiàn)的就是讓tr能定位。
首先想到的方法是給tr設(shè)置relative,用ie6/7測試以下代碼:

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
給tr設(shè)置relative后就能相對table定位了,看來很簡單啊,但問題是這個方法ie8和ff都無效,而且存在很多問題,所以很快就被拋棄了。
ps:該效果用來做tr的拖動會很方便。
接著想到的是給table插入一個新tr,克隆原來的tr,并設(shè)置這個tr為fixed(ie6為absolute),例如:
Code

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
第一個問題是fixed的tr在ie7中不能進行定位,而且td在定位后并不能保持在表格中的布局,這樣在原表格插tr就沒意義了。
ps:fixed的相關(guān)應(yīng)用可參考仿LightBox效果。

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


程序說明

【克隆table】

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

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


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

克隆之后再設(shè)置樣式:

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


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

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
只要給table設(shè)置width(style或本身的width屬性),不管設(shè)置padding和border是多少,offsetWidth都等于width的值。
經(jīng)測量offsetWidth是沒錯的,那就是說是table的width設(shè)置的問題。
w3c的table部分中說width屬性是the desired width of the entire table,我估計entire就是包含了padding和border,找不到什么其他說明,先這么理解吧。
定位方面,除了不支持fixed的ie6用absolute,其他都使用fixed定位。


【克隆tr】

table有一個rows集合,包括了table的所有tr(包括thead和tfoot里面的)。
程序的Clone方法會根據(jù)其參數(shù)克隆對應(yīng)索引的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屬性用來指定邊框?qū)挾龋瑃able特有的frame屬性是用來設(shè)置或獲取表格周圍的邊框顯示的方式。
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是默認值,但不設(shè)置的話其實是一個空值,這時四條邊框都會顯示。
還有frame對style設(shè)置的border沒有效果,測試下面代碼:

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
這里還可以看到如果同時設(shè)置table的border和style的border,那table的border就會失效。

程序中為了更美觀會自動去掉新table上面和下面的邊框,包括frame和style的:
Code
復制代碼 代碼如下:
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è)置collapse之后會比較麻煩,在ie6/ie7中測試:

Code

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]

后兩個的轉(zhuǎn)換還可以接受,所以在設(shè)置frame之前還是判斷一下border先。


【獲取背景色】

如果td是背景透明的話顯然不太美觀,最好是找一個合適的顏色來填充。
程序用的方法是,從當前td開始找,如果背景是透明的話,就再從父節(jié)點中找,直到找到有背景色為止。
一般來說透明的屬性值是"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的區(qū)別。
先看看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.
很簡單,就是節(jié)點的父節(jié)點,看過dom都知道。

再看看比較容易區(qū)分的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,返回至少符合以下一個條件的最接近該節(jié)點的元素:1,元素的position不是static;2,是body元素;3,源元素的position是static,祖先元素中的以下元素:td,th或table。
4,返回null。
其中第三點是最常見的情況,詳細可以看下面的測試:

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
可見offsetParent跟parentNode的區(qū)別還是很大的。

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

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
可以看到當父節(jié)點的nodeType不是1,即不是element節(jié)點的話,它的parentElement就會是null。
這就明白了名字中“Element”的含義了。


【設(shè)置td寬度】

接下來就要設(shè)置td寬度了,要獲取某元素的寬度可以通過以下方法:
1,支持defaultView的可以直接用getComputedStyle獲取width。
2,獲取offsetWidth,再減去border和padding的寬度。
這個本來也可以,但td的border寬度的獲取比較麻煩,下面有更方便的方法。
3,獲取clientWidth,再減去padding的寬度。
這個跟方法2差不多,但更簡單方便。

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

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


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

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


【borderCollapse】

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

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

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
可以看到使用collapse之后,相鄰td的邊框都合并成一條而且是以相鄰邊框中寬度較大的那條為準。
那td跟table之間呢,參考下面的例子:

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
可見table和td之間也是遵從同樣規(guī)則。
還有的是當設(shè)置了collapse那cellspacing就無效了。順便說說border-spacing,它其實就是cellspacing在css中的樣式形式,只是ie在ie8才開始支持,詳細可以看mozilla的說明。

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

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
前者用的collapse,后者是用table背景色模擬,雖然效果都一樣,但前者顯然較好,才是真正的“邊框”。

在使用了collapse之后,要寫一個通用的獲取邊框?qū)挾瘸绦驎兊檬致闊矣行┣闆r下甚至沒辦法判斷獲取。
詳細情況這里就不細說了,有興趣研究的話可以看看w3c的The collapsing border model,當然要想全部了解的話還要在各個瀏覽器中研究。


【元素位置】

table的樣式設(shè)置好后,還需要獲取原table和原tr的位置參數(shù),為后面的元素定位做準備。
要獲取某個元素相對文檔的位置,傳統(tǒng)的做法是獲取對象的offsetLeft/offsetTop,然后不斷獲取offsetParent的offsetLeft/offsetTop,直到找不到offsetParent為止。
得到的結(jié)果就是相對文檔的位置了,上面已經(jīng)介紹過offsetParent,原理應(yīng)該都明白了吧。
程序的SetRect設(shè)置區(qū)域?qū)傩苑椒ㄖ幸彩褂昧诉@個思路:
復制代碼 代碼如下:
//獲取原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;

不過這里介紹一個更好的方法,通過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。
通過下面的測試可以看到兩個方法返回的結(jié)果都是相同的:

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
程序中如果支持getBoundingClientRect就會用它來獲取位置參數(shù):
復制代碼 代碼如下:
//用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的產(chǎn)物,但已經(jīng)是w3c的標準,而且ff3和Opera都已經(jīng)支持了這個方法,基本可以放心使用,除了chrome。
這里只是簡單介紹,想了解更多可以看w3c的View Module部分。

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

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


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


【元素定位】

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

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


定位的第一步就是判斷是否需要定位,這里的判斷標準有兩個,第一個是原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屬性,它是用來指定否自動定位的。
如果自動定位的話當原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){}


當符合所有的條件就可以進行定位了,如果是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è)置。

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

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


【覆蓋select】

只要用到了定位,就不得不面對一個老對手“ie6的select”。
我在之前的文章也介紹過一些解決方法(參考這里的覆蓋select),這里不能直接隱藏select,那看來只能用iframe了。
但用iframe有一個很大的問題,在ie6測試下面的代碼,并拖動滾動條:

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
可以看到,即使是iframe,在拖動滾動條的時候,select仍然在后面閃啊閃,在本程序中這個現(xiàn)象會尤其明顯。
看來還得用隱藏select的方法,最好的做法是只隱藏在新table后面的select,而不影響其他select的正常顯示。
那關(guān)鍵就是如何判斷select是否在新table后面,這個可以通過位置坐標判斷,剛好可以用到上面的getBoundingClientRect。
一般的思路是判斷新table和select的坐標,根據(jù)位置判斷select的顯示和隱藏。
但如果有多個實例,可能會導致select在一個實例中要隱藏,卻在另一個要顯示的情況。

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

程序中的SetSelect方法就是用來判斷和設(shè)置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);//防止多個實例沖突
//設(shè)置隱藏
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】

在測試的時候發(fā)現(xiàn)Chrome一個bug,測試下面代碼:

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
一個毫不相干的操作居然令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ù)是要定位的位置百分比。

具體使用請參考實例。


程序源碼

[Ctrl+A 全選 注:如需引入外部Js需刷新才能執(zhí)行]
下載完成測試代碼
轉(zhuǎn)載請注明出處:http://www.cnblogs.com/cloudgamer/

JavaScript技術(shù)JavaScript Table行定位效果,轉(zhuǎn)載需保留來源!

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 欧美亚洲综合激情在线 | 一色屋色费精品视频在线看 | 特大毛片 | 一级做性色a爰片久久毛片 一级做性色a爰片久久毛片免费 | 好吊妞视频988在线播放 | 91国视频在线 | 亚洲激情文学 | 婷婷综合五月 | 涩涩视频在线看 | 国产精品美女在线观看 | 日本高清不卡二区 | 四虎影视永久免费 | 久久黄色大片 | 亚洲视频一二三 | 国产乱子精品免费视观看片 | 国产精品视频李雅 | 伊人草草| 巨胸喷奶水www久久久免费观看 | 五月婷网 | 福利视频一区青娱 | 国内免费视频成人精品 | 视频成人永久免费看 | 国产精品欧美亚洲韩国日本不卡 | 国产美女毛片 | 综合久久久久久久综合网 | 黄色免费观看 | 久久99精品国产 | 精品午夜久久网成年网 | 亚洲国产一区在线 | 免费在线观看一区二区 | 麻豆高清视频 | 亚洲视频 欧美视频 | 国产美女一区二区在线观看 | 热久久国产欧美一区二区精品 | 精品麻豆国语国拍视频在线 | 久久全国免费久久青青小草 | 色视频免费在线观看 | 色多多入口 | 亚洲天堂色视频 | 91精品视频在线免费观看 | 久久伊人中文字幕有码 |