zoukankan      html  css  js  c++  java
  • 近来有客户要求用table显示一大串数据,由于滚动后就看不到表头,很不方便,所以想到这个效果。

    程序原理

    一开始的需求只是表头部分在滚动时能一直固定在头部,那首先要实现的就是让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,还有是要准确的定位,这些请看后面的程序说明。


    程序说明

    【克隆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属性设空。
    克隆之后再设置样式:
    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; 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,找不到什么其他说明,先这么理解吧。
    http://www.w3.org/TR/html4/struct/tables.html#adef-width-TABLE
    定位方面,除了不支持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.
    http://www.w3.org/TR/html4/struct/tables.html#adef-frame
    这些值指明了要显示的边框。要留意的是虽然说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{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里却是"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.
    http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-1060184317
    很简单,就是节点的父节点,看过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.
    http://www.w3.org/TR/cssom-view/#elementview-offsetparent
    这里主要有四点:
    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”的含义了。


    【设置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部分。
    http://www.w3.org/TR/CSS2/tables.html#width-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.
    https://developer.mozilla.org/pt/CSS/border-collapse
    意思是在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;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-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-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;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的说明。
    https://developer.mozilla.org/En/CSS/Border-spacing

    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;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,当然要想全部了解的话还要在各个浏览器中研究。
    http://www.w3.org/TR/CSS2/tables.html#collapsing-borders
    【元素位置】

    table的样式设置好后,还需要获取原table和tr的位置参数,为后面的元素定位做准备。
    要获取某个元素的相对文档的位置,传统的做法是获取对象的offsetLeft/offsetTop,然后分别加上offsetParent的offsetLeft/offsetTop,直到找不到offsetParent为止。
    得到的结果就是相对文档的位置了,上面已经介绍过offsetParent,原理应该都明白了吧。
    程序的SetRect设置区域属性方法中也使用了这个思路:
    //获取原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...
    https://developer.mozilla.org/En/DOM/Element.getBoundingClientRect
    返回一个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;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部分。
    http://www.w3.org/TR/cssom-view/#the-getclientrects

    获取原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的高度。


    【元素定位】

    万事俱备,只欠定位了。
    由于要根据窗口滚动状态来判断计算定位,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就会定位到视框顶部,离开底部新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;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方法,其参数是要定位的位置百分比。

    具体使用请参考实例。

    程序源码

    JScript 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坐标参数 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(this, this.SetPos)); addEventHandler(window, "scroll", Bind(this, this.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(this, function(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(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(); }, //设置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);//防止多个实例冲突 //设置隐藏 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 = []; } };
  • 相关阅读:
    RestTemplateConfig写法,用于配置Template引擎模板
    RedisUtil写法,好用
    ftpUtil写法,记录一下
    生成唯一id写法,雪花算法
    base64编码转图片,图片转base64编码
    MultipartFile上传图片的写法,记录一下。
    httpClientUtil的put请求
    httpClientUtil的post请求
    python time和datetime
    ubuntu16.04TLS软件源update
  • 原文地址:https://www.cnblogs.com/y0umer/p/3839551.html
Copyright © 2011-2022 走看看