JavaScript Table行定位效果(三)

原创|其它|编辑:郝浩|2009-05-20 10:16:24.000|阅读 892 次

概述:近来有客户要求用table显示一大串数据,由于滚动后就看不到表头,很不方便,所以想到这个效果。 上次做table排序对table有了一些了解,这次更是深入了解了一番,发现table原来是这么不简单。

# 界面/图表报表/文档/IDE等千款热门软控件火热销售中 >>

【元素定位】

万事俱备,只欠定位了。
由于要根据窗口滚动状态来判断计算定位,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程序。


【覆盖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(thisfunction(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方法,其参数是要定位的位置百分比。

具体使用请参考实例。


程序源码


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坐标参数
    this._oRowTop = this._oRowBottom = 0;//记录原tr坐标参数
    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(thisthis.SetPos));
    addEventHandler(window, 
"scroll", Bind(thisthis.Run));
    
    
this._oTable.parentNode.insertBefore(this._nTable, this._oTable);
    
this.Clone();
};
TableFixed.prototype 
= {
  
//设置默认属性
  SetOptions: function(options) {
    
this.options = {//默认值
        Index:    0,//tr索引
        Auto:    true,//是否自动定位
        Pos:    0,//自定义定位位置百分比(0到1)
        Hide:    false//是否隐藏(不显示)
    };
    Extend(
this.options, options || {});
  },
  
//克隆表格
  Clone: function(index) {
    
//设置table样式
    this._style.width = this._oTable.offsetWidth + "px";
    
this._style.position = isIE6 ? "absolute" : "fixed";
    
this._style.zIndex = 100;
    
//设置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;
    
//设置td样式
    var nTds = this._nTable.rows[0].cells;
    forEach(
this._oRow.cells, Bind(thisfunction(o, i){
        
var css = CurrentStyle(o), style = nTds[i].style;
        
//设置td背景
        style.backgroundColor = this.GetBgColor(o, css.backgroundColor);
        
//设置td的width,没考虑ie8/chrome设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;
  },
  
//设置坐标属性
  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;
    }
  },
  
//设置新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() {
    
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范围内
            if(posTop > this._oTableTop && posTop + this._nTableHeight < this._oTableBottom){
                
//定位
                if(isIE6){
                    
this._style.top = posTop + "px";
                    
this._style.left = this._oTableLeft + "px";
                    setTimeout(Bind(
thisthis.SetSelect), 0);//iebug
                }else{
                    
this._style.top = viewTop + "px";
                    
this._style.left = this._oTableLeft - left + "px";
                }
                
return;
            }
        }
    }
    
//隐藏
    this._style.top = "-99999px";
    isIE6 
&& this.ResetSelect();
  },
  
//设置select集合
  SetSelect: function() {
    
this.ResetSelect();
    
var rect = this._nTable.getBoundingClientRect();
    
//把需要隐藏的放到_selects集合
    this._selects = Filter(this._oTable.getElementsByTagName("select"), Bind(thisfunction(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;
        }
    }))
  },
  
//恢复select样式
  ResetSelect: function() {
    forEach(
this._selects, function(o){ !--o._count && (o.style.visibility = o._css); });
    
this._selects = [];
  }
};

 

下载完成测试代码 

转载请注明出处:http://www.cnblogs.com/cloudgamer/

标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com

文章转载自:博客园

为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP