近来有客户要求用table显示一大串数据,由于滚动后就看不到表头,很不方便,所以想到这个效果。 上次做table排序 对table有了一些了解,这次更是深入了解了一番,发现table原来是这么不简单。 还不清楚这个效果叫什么,有点像表头固定的效果,就叫行定位吧,本来想把列定位也做出来,但暂时还没这个需求,等以后有时间再弄吧。 在淘宝的商品搜索页 也看到类似的效果,但淘宝的不是table,而是li,而我这个是用在table上的。 要说明一下的是,我这个效果是用在一些普通的产品列表,当数据比较多时提高用户体验,而不是单单做数据显示,跟excel那样的方式是不同的。
效果预览
为方便预览,建议缩小浏览器。
点击行选择克隆行:当前克隆第 1 行 ps:为方便预览,建议缩小浏览器。 注意,使用ie8的兼容性视图会有偏移。
程序原理
一开始的需求只是表头部分在滚动时能一直固定在头部,那关键要实现的就是让tr能定位。 首先想到的方法是给tr设置relative,用ie6/7测试以下代码:
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 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),例如:
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 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,还有是要准确的定位,这些请看后面的程序说明。
程序说明
【克隆table】
克隆一个元素用cloneNode就可以了,它有一个bool参数,表示克隆是否包含子节点。 程序第一步就是克隆原table:
this
._oTable
=
$$(table);
this
._nTable
=
this
._oTable.cloneNode(
false
);
this
._nTable.id
=
""
;
要注意虽然ie的cloneNode参数是可选的(默认是false),但在ff是必须的,建议使用时都写上参数。 还要注意的是id属性也会被克隆,也就是克隆后会有两个相同id的元素(如果克隆对象有设置的话),这很容易会导致其他问题,程序会把克隆table的id属性设空。 ps:table请用class来绑定样式,用id的话新table就获取不了样式了。 克隆之后再设置样式:
$$D.setStyle(
this
._nTable, { width:
this
._oTable.offsetWidth
+
"
px
"
, position: $$B.ie6
?
"
absolute
"
:
"
fixed
"
, zIndex:
99
, borderTopWidth:
0
, borderBottomWidth:
0
});
一般来说offsetWidth是width+padding+border的结果,但table比较特别,测试下面的代码:
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 ="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定位。
【克隆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没有效果,测试下面代码:
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 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的:
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 ; } }
其中空值在设置collapse之后会比较麻烦,在ie6/ie7中测试:
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" > .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先。
【获取背景色】
如果td是背景透明的话显然不太美观,最好是找一个合适的颜色来填充。 程序用的方法是,从当前td开始找,如果背景是透明的话,就再从父节点中找,直到找到有背景色为止。 一般来说透明的属性值是"transparent",但在chrome和safari里却是"rgba(0, 0, 0, 0)",所以用了一个属性来保存透明值:
_transparent: $$B.chrome
||
$$B.safari
?
"
rgba(0, 0, 0, 0)
"
:
"
transparent
"
,
并在_getBgColor获取背景色程序中使用:
var
bgc
=
""
;
while
(bgc
===
this
._transparent
&&
(node
=
node.parentNode)
!=
document) { bgc
=
$$D.getStyle(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。 其中第三点是最常见的情况,详细可以看下面的测试:
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 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就没必要弄这么一个东西出来了,测试下面的代码:
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 > < 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”的含义了。
【设置td宽度】
接下来就要设置td宽度了,要获取某元素的宽度可以通过以下方法: 1,支持defaultView的可以直接用getComputedStyle获取width。 2,获取offsetWidth,再减去border和padding的宽度。 这个本来也可以,但td的border宽度的获取比较麻烦,下面有更方便的方法。 3,获取clientWidth,再减去padding的宽度。 这个跟方法2差不多,但更简单方便。
注意ie的currentStyle不像getComputedStyle能获取准确值,而只是一个设置值,像百分比、auto这些并不会自动转成准确值,即使是得到准确值也不一定是实际值,例如td即使设置一个很大的准确值,实际值也不会超过table本身的宽度。 所以在td这种比较特殊的结构中,不要用currentStyle来获取td的宽度。 对于支持defaultView的当然可以直接获取,否则就用上面的方法3来获取:
style.width
=
(document.defaultView
?
parseFloat(getStyle(o,
"
width
"
)) : ( o.clientWidth
-
parseInt(getStyle(o,
"
paddingLeft
"
))
-
parseInt(getStyle(o,
"
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会共用边框。看下面的例子会更明白:
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" > .t{ line-height : 40px ; width : 200px ; } .t td{ border : 5px solid #999 ; } </ style > < table class ="t" style ="border-collapse:collapse;" > < tr > < td width ="50" > </ td > < td style ="border-left-width:10px; border-left-style:dotted;" > </ td > < td width ="50" > </ td > </ tr > </ table > < table class ="t" > < tr > < td width ="50" > </ td > < td style ="border-left-width:10px; border-left-style:dotted;" > </ td > < td width ="50" > </ td > </ tr > </ table > </ body > </ html >
可以看到使用collapse之后,相邻td的边框都会变成一条。 再看w3c中Border conflict resolution 的部分,可知相邻边框会按以下规则获取优先级最高的边框: 1,'border-style'是'hidden'的优先级最高; 2,'border-style'是'none'的优先级最低; 3,'border-width'大的优先级更高; 4,接着按以下'border-style'由高到低排列:'double', 'solid', 'dashed', 'dotted', 'ridge', 'outset', 'groove', 'inset'; 5,接着按以下border所属对象由高到低排列:cell,row,row group,column,column group,table; 6,以上都一样的话,最后按left优先,top优先排列。
ps:从Border styles 可知'hidden'和'none'的'border-style'基本是一样的,区别就在于collapse时的优先级。
那td跟table之间呢,参考下面的例子:
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" > .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;" > </ td > < td > </ td > < td width ="50" > </ td > </ tr > </ table > < br /> < table class ="t" id ="t2" style ="border-left:10px dotted #999;" > < tr > < td width ="50" > </ td > < td > </ td > < td width ="50" > </ td > </ tr > </ table > </ body > </ html >
可见table和td之间也是遵从同样规则。 还有的是当设置了collapse那cellspacing就无效了。顺便说说border-spacing,它其实就是cellspacing在css中的样式形式,只是ie在ie8才开始支持,详细可以看mozilla的说明 。
collapse的一个常见应用是做边框表格,例如1px边框的表格:
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" > .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" > </ td > < td > </ td > < td width ="50" > </ td > </ tr > </ table > < table class ="t t2" cellspacing ="1" > < tr > < td width ="50" > </ td > < td > </ td > < td width ="50" > </ td > </ tr > </ table > </ body > </ html >
前者用的collapse,后者是用table背景色模拟,虽然效果都一样,但前者显然较好,才是真正的“边框”。
在使用了collapse之后,要写一个通用的获取边框宽度程序会变得十分麻烦,而且有些情况下甚至没办法判断获取。 详细情况这里就不细说了,有兴趣研究的话可以看看w3c的The collapsing border model ,当然要想全部了解的话还要在各个浏览器中研究。
【元素位置】
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。 通过下面的测试可以看到两个方法返回的结果都是相同的:
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" > .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更方便快捷。 这个方法虽然是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
._nTableViewTop
=
(
this
._viewHeight
-
this
._nTableHeight)
*
this
._pos;
定位范围实际上是从视框顶部到视框高度减去新table高度的范围内的,所以计算时要先把视窗高度减去新table的高度。
【元素定位】
万事俱备,只欠定位了。 由于要根据窗口滚动状态来判断计算定位,scrollTop/scrollLeft的获取必不可少。 但要注意在chrome/safari中就算用了DOCTYPE,也要用document.body来获取scrollTop/scrollLeft,尽管它确实有document.documentElement。 定位的第一步就是判断是否需要定位,这里的判断标准有两个,第一个是原tr是否超过了视窗范围,还有是新table要显示的位置是否在原table的显示范围内。 第一点可以通过原tr位置的顶部和底部是否超过视窗的顶部和底部来判断:
Code
var top = $$D.getScrollTop(), left = $$D.getScrollLeft() ,outViewTop = this ._oRowPos.top < top, outViewBottom = this ._oRowPos.bottom > 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)) ,posTop
=
viewTop
+
top;
接着就判断新table要显示的位置是否在原table的显示范围内,这个可以通过新table位置的顶部和底部是否超过原table的顶部和底部来判断:
if
( posTop
>
this
._oTablePos.top
&&
posTop
+
this
._nTableHeight
<
this
._oTablePos.bottom ){ }
当符合所有的条件就可以进行定位了,如果是fixed定位的就使用相对视窗的top值:
tStyle.top
=
viewTop
+
"
px
"
; tStyle.left
=
this
._oTablePos.left
-
left
+
"
px
"
;
像ie6是absolute定位的就要使用相对文档的top值:
tStyle.top
=
posTop
+
"
px
"
; tStyle.left
=
this
._oTablePos.left
+
"
px
"
;
考虑到左右滚动的情况,left也必须设置。
当然不符合条件就会隐藏新table,程序中给top设置一个很大的负值来间接“隐藏”它。 用负值是因为这样不会把ie6的页面拉长,不用display是因为上面需要获取它的offsetHeight,如果用display隐藏就获取不了啦。
最后把run程序绑定到window的scroll事件中就可以了,而window在resize时视框高度会发生变化,所以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仍然在后面闪啊闪,在本程序中这个现象会尤其明显。 看来还得用隐藏select的方法,最好的做法是只隐藏在新table后面的select,而不影响其他select的正常显示。 那关键就是如何判断select是否在新table后面,这个可以通过位置坐标判断。 一般的思路是判断新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的:
Code
this ._resetSelect(); var rect = $$D.clientRect( this ._nTable); this ._selects = $$A.filter( this ._oTable.getElementsByTagName( " select " ), $$F.bind( function (o){ var r = $$D.clientRect(o); 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 ; } }, this ))
其中_resetSelect方法是用来恢复显示select的:
$$A.forEach(
this
._selects,
function
(o){
!--
o._count
&&
( o.style.visibility
=
o._css ); } );
this
._selects
=
[];
但这个方法在快速滚屏时还是无能为力,而且select越多效率也随之下降,各位有更好方法的话欢迎交流。
使用说明
实例化一个TableFixed对象只需要一个参数table的id:
new
TableFixed(
"
idTable
"
);
实例化时有4个可选属性: index: 0,//tr索引 auto: true,//是否自动定位 pos: 0,//自定义定位位置百分比(0到1) hide: false//是否隐藏(不显示)
其中index和pos在实例化之后就不能使用。 要修改克隆行可以用clone方法,其参数是要克隆tr的索引。 要修改自定义定位位置可以用setPos方法,其参数是要定位的位置百分比。
具体使用请参考实例。
程序源码
Code
var TableFixed = function (table, options){ this ._oTable = $$(table); // 原table this ._nTable = this ._oTable.cloneNode( false ); // 新table this ._nTable.id = "" ; // 避免id冲突 this ._oTablePos = {}; // 记录原table坐标参数 this ._oRowPos = {}; // 记录原tr坐标参数 this ._viewHeight = this ._oTableHeight = this ._nTableHeight = 0 ; // 记录高度 this ._nTableViewTop = 0 ; // 记录新table视框top this ._selects = []; // select集合,用于ie6覆盖select this ._setOptions(options); this ._index = this .options.index; this ._pos = this .options.pos; this .auto = !! this .options.auto; this .hide = !! this .options.hide; $$E.addEvent(window, " resize " , $$F.bind( this .setPos, this )); $$E.addEvent(window, " scroll " , $$F.bind( this .run, this )); this ._oTable.parentNode.insertBefore( this ._nTable, this ._oTable); this .clone(); }; TableFixed.prototype = { // chrome/safari透明用rgba(0, 0, 0, 0) _transparent: $$B.chrome || $$B.safari ? " rgba(0, 0, 0, 0) " : " transparent " , // 设置默认属性 _setOptions: function (options) { this .options = { // 默认值 index: 0 , // tr索引 auto: true , // 是否自动定位 pos: 0 , // 自定义定位位置百分比(0到1) hide: false // 是否隐藏(不显示) }; $$.extend( this .options, options || {}); }, // 克隆表格 clone: function (index) { // 设置table样式 $$D.setStyle( this ._nTable, { width: this ._oTable.offsetWidth + " px " , position: $$B.ie6 ? " absolute " : " fixed " , zIndex: 99 , borderTopWidth: 0 , borderBottomWidth: 0 }); // 设置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 ; } } // 设置td样式 var nTds = this ._nTable.rows[ 0 ].cells, getStyle = $$D.getStyle; $$A.forEach( this ._oRow.cells, $$F.bind( function (o, i){ var style = nTds[i].style; // 设置td背景 style.backgroundColor = this ._getBgColor(o); // 设置td的width,没考虑ie8/chrome设scroll的情况 style.width = (document.defaultView ? parseFloat(getStyle(o, " width " )) : ( o.clientWidth - parseInt(getStyle(o, " paddingLeft " )) - parseInt(getStyle(o, " paddingRight " )) )) + " px " ; }, this )); // 获取table高度 this ._oTableHeight = this ._oTable.offsetHeight; this ._nTableHeight = this ._nTable.offsetHeight; // 设置坐标属性 this ._oTablePos = $$D.rect( this ._oTable); // 获取原table位置 this ._oRowPos = $$D.rect( this ._oRow); // 获取原tr位置 this .setPos(); }, // 获取背景色 _getBgColor: function (node) { var bgc = "" ; // 不要透明背景(没考虑图片背景) while (bgc === this ._transparent && (node = node.parentNode) != document) { bgc = $$D.getStyle(node, " backgroundColor " ); } return bgc === this ._transparent ? " #fff " : bgc; }, // 设置新table位置属性 setPos: function (pos) { // 设置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(); }, // 运行 run: function () { var tStyle = this ._nTable.style; if ( ! this .hide){ var top = $$D.getScrollTop(), left = $$D.getScrollLeft() // 原tr是否超过顶部和底部 ,outViewTop = this ._oRowPos.top < top, outViewBottom = this ._oRowPos.bottom > 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范围内 if ( posTop > this ._oTablePos.top && posTop + this ._nTableHeight < this ._oTablePos.bottom ){ // 定位 if ( $$B.ie6 ){ tStyle.top = posTop + " px " ; tStyle.left = this ._oTablePos.left + " px " ; setTimeout($$F.bind( this ._setSelect, this ), 0 ); // iebug } else { tStyle.top = viewTop + " px " ; tStyle.left = this ._oTablePos.left - left + " px " ; } return ; } } } // 隐藏 tStyle.top = " -99999px " ; $$B.ie6 && this ._resetSelect(); }, // 设置select集合 _setSelect: function () { this ._resetSelect(); var rect = $$D.clientRect( this ._nTable); // 把需要隐藏的放到_selects集合 this ._selects = $$A.filter( this ._oTable.getElementsByTagName( " select " ), $$F.bind( function (o){ var r = $$D.clientRect(o); 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 ; } }, this )) }, // 恢复select样式 _resetSelect: function () { $$A.forEach( this ._selects, function (o){ !-- o._count && ( o.style.visibility = o._css ); } ); this ._selects = []; } };
下载完成测试代码