Home  >  Article  >  Web Front-end  >  JavaScript Table row positioning effect_javascript skills

JavaScript Table row positioning effect_javascript skills

WBOY
WBOYOriginal
2016-05-16 18:52:401105browse

Last time I did table sorting, I gained some understanding of the table. This time I learned more about it and found that the table is so complicated.
I don’t know yet what this effect is called, let’s just call it row positioning. I originally wanted to do column positioning, but there is no need for it yet. I will do it when I have time.
Program Principle
The initial requirement is that the head part of the watch can always be fixed on the head when scrolling. The key thing to achieve is to enable tr to be positioned.
The first thing that comes to mind is to set relative for tr and test the following code with ie6/7:


[Ctrl A Select all Note: If you need to introduce external Js, you need to refresh to execute ]

After setting relative to tr It seems very simple to be able to position relative to the table, but the problem is that this method is invalid for both ie8 and ff, and there are many problems, so it was quickly abandoned.
ps: This effect is very convenient for dragging tr.
The next thing I thought of was to insert a new tr into the table, clone the original tr, and set this tr to fixed (ie6 is absolute), for example:
Code

[Ctrl A Select all Note: If you need to introduce external Js, you need to refresh to execute ]

The first problem is fixed The tr cannot be positioned in IE7, and the td cannot maintain the layout in the table after being positioned, so it is meaningless to insert tr in the original table.
ps: For related applications of fixed, please refer to Imitation LightBox effect.

The last method I used was to create a new table, clone the source tr into the new table, and then position the new table to achieve the effect.
There are two key points in using this method. First, you need to make a TR with the highest degree of simulation possible, and secondly, you need to accurately position it. Please see the following program instructions for these.


Procedure Description

【Clone table】

To clone an element, just use cloneNode. It has a bool parameter indicating whether the clone contains child nodes.
The first step of the program is to clone the original table:

this ._oTable = $(table);//source table
this._nTable = this._oTable.cloneNode(false);//New table
this._nTable.id = "";//Avoid id conflicts


It should be noted that although the cloneNode parameter of ie is optional (default is false), it is required in ff. It is recommended to write the parameters when using it.
Also note that the id attribute will also be cloned, that is, there will be two elements with the same id after cloning (if the clone object is set), which can easily lead to other problems. The program will clone the table Leave the id attribute empty.
ps: Please use class to bind the style to the table. If you use id, the new table will not be able to obtain the style.

Clone and then set the style:

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


Generally speaking, offsetWidth is the result of width padding border, but table is special. Test the following code:


[Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

只要给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);
}


Because the program allows modification of the cloned tr, it will determine whether it has been inserted. If not, it will directly appendChild, otherwise replace the original tr with replaceChild.


[table’s border and frame attributes]

The border attribute of table is used to specify the border width, and the table-specific frame attribute is used to set or get the display method of the border around the table.
The frame part of the w3c label indicates that the frame can have the following values:
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.
These values ​​specify the border to be displayed. It should be noted that although void is the default value, if it is not set, it is actually a null value, and all four borders will be displayed.
Also, frame has no effect on the border set by style. Test the following code:


[Ctrl A Select all Note: If you need to introduce external Js, you need to refresh to execute ]

Here you can also see if If you set the table's border and the style's border at the same time, the table's border will become invalid.

In order to make it more beautiful, the program will automatically remove the borders above and below the new table, including frame and style:
Code
Copy code The code is as follows:

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;


The null value will be more troublesome after setting collapse. Tested in ie6/ie7:

Code

[Ctrl A Select all Note: If you need to introduce external Js, you need to refresh to execute ]


The last two The conversion is acceptable, so it is better to judge the border before setting the frame.


[Get the background color]

If the td has a transparent background, it will obviously not look good. It is best to find a suitable color to fill it.
The method used by the program is to start searching from the current td. If the background is transparent, then search from the parent node until it finds a background color.
Generally speaking, the transparent attribute value is "transparent", but in Chrome it is "rgba(0, 0, 0, 0)", so an attribute is used to save the transparent value:
Copy code The code is as follows:

this._transparent = isChrome ? "rgba(0, 0, 0, 0)" : "transparent";

and use it in GetBgColor to get the background color program:
Copy code The code is as follows:

while (bgc == this._transparent && (node ​​= node.parentNode) != document) {
bgc = CurrentStyle(node).backgroundColor;
}
return bgc == this._transparent ? "#fff" : bgc;

If everything is transparent, it will return white (#fff).
The background of the image is not considered here. After all, the image may not necessarily cover the entire background.
[parentNode/offsetParent/parentElement]

ParentNode is used above. By the way, let’s talk about the difference between it and offsetParent and parentElement.
First look at the description of parentNode in 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.
It is very simple, it is the parent node of the node. Anyone who has seen DOM knows it.

Look at offsetParent, which is easier to distinguish. It is vague in both mozilla and msdn, but it is clearer in 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 .
There are four main points here:
1. If it is a root element, body element or the position of the element is fixed, null will be returned;
2. If it is an area element, the closest map element will be returned. ;
3, returns the element closest to the node that meets at least one of the following conditions: 1. The position of the element is not static; 2. It is a body element; 3. The position of the source element is static, and the following elements among the ancestor elements: td, th or table.
4, returns null.
The third point is the most common situation. For details, you can see the test below:


[Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

可见offsetParent跟parentNode的区别还是很大的。

而parentNode跟parentElement除了前者是w3c标准,后者只ie支持,其他的区别就不是那么明显了。
在ie中大部分情况下两者的效果是一样的,当然如果是一模一样的话ie就没必要弄这么一个东西出来了,测试下面的代码:

[Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

可以看到当父节点的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";


But no matter which method here, there is a problem, that is, scroll occurs, but fortunately, even if the td element is set to overflow to scroll, the scroll bar will not appear, except for ie8 and chrome.
The program does not handle this situation. After all, it is not common to set scroll for td, and there are not many browsers that support this. There is no need to spend too much time here.
ps: Regarding the automatic adjustment of td width, please refer to the table-layout part of w3c .

If there are settings that affect the original td structure, such as colspan, please pay attention. The wrong structure may cause some abnormal deformation.
If the structure or content of the original table is modified, the Clone method should be executed to reconstruct the new table.
This part is more important for the experience. If it is not set properly, it will feel deformed, which is very unsightly.


【borderCollapse】

It is mentioned above that obtaining the border width of td is troublesome, but how troublesome is it?
If it is just a general case, just get the width through borderLeftWidth and borderRightWidth.
ps: If borderStyle is "none", then the border will be invalid, so if you want to get the border width, it is best to first determine whether borderStyle is "none".

But table has a special style borderCollapse, which sets the table's border model.
It has two values, namely separate (separate, default value) and collapse (merge).
separate is the effect we usually see. Here we mainly discuss collapse. Let’s first see what mozilla says:
In the collapsed border model, adjacent table cells share borders .
means that in the collapse border model, adjacent tds will share the border. It will be clearer if you look at the following example:


[Ctrl A Select all Note: If you need to introduce external Js, you need to refresh to execute ]

You can see that after using collapse , the borders of adjacent TDs are merged into one and the one with the larger width among the adjacent borders shall prevail.
What about between td and table? Please refer to the following example:

[Ctrl A Select all Note: If you need to introduce external Js, you need to refresh to execute ]

Visible between table and td It also follows the same rules.
Another thing is that when collapse is set, cellspacing is invalid. By the way, border-spacing is actually the style form of cellspacing in CSS. It was only supported by IE in IE8. For details, please see mozilla’s instructions . A common application of

collapse is to make a border table, such as a table with a 1px border:


[Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

前者用的collapse,后者是用table背景色模拟,虽然效果都一样,但前者显然较好,才是真正的“边框”。

在使用了collapse之后,要写一个通用的获取边框宽度程序会变得十分麻烦,而且有些情况下甚至没办法判断获取。
详细情况这里就不细说了,有兴趣研究的话可以看看w3c的The collapsing border model,当然要想全部了解的话还要在各个浏览器中研究。


【元素位置】

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...
返回一个TextRectangle对象,包含left, top, right和bottom几个只读属性,以px为单位来表示边界框相对视窗左上角的位置。(偶英文烂啊)
注意是相对视窗,不是文档哦,如果要相对文档还必须加上scrollLeft/scrollTop。
通过下面的测试可以看到两个方法返回的结果都是相同的:

[Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

程序中如果支持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部分

获取原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就会定位到视框顶部,原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";


For example, if ie6 is positioned absolutely, you need to use the top value of the relative document:

this ._style.top = posTop "px ";
this._style.left = this._oTableLeft "px";


Considering left and right scrolling, left must also be set.

Of course, if the conditions are not met, the new table will be hidden. The program sets a large negative value for top to indirectly "hide" it.
The negative value is used because it will not stretch the IE6 page. The reason why display is not used is because its offsetHeight needs to be obtained above. If it is hidden by display, it cannot be obtained.

Finally, just bind the Run program to the scroll event of the window. The height of the view frame will change when the window is resized, so the resize event needs to be bound to the SetPos program.


【Cover select】

As long as you use positioning, you have to face an old rival "ie6's select".
I have also introduced some solutions in previous articles (Refer to the overlay select here). You cannot hide the select directly here, so it seems that you can only use iframe.
But there is a big problem with iframe. Test the following code in ie6 and drag the scroll bar:


[Ctrl A Select all Note: If you need to introduce external Js, you need to refresh to execute ]

You can see that even if iframe, when dragging the scroll bar, the select still flashes behind it. This phenomenon will be especially obvious in this program.
It seems that you have to use the method of hiding the select. The best way is to only hide the select behind the new table without affecting the normal display of other selects.
The key is how to judge whether the select is behind the new table. This can be judged by the position coordinates, and the above getBoundingClientRect can be used.
The general idea is to determine the coordinates of the new table and select, and determine the display and hiding of the select based on the position.
But if there are multiple instances, it may cause the select to be hidden in one instance but displayed in another.

In order to resolve the conflict, the program adds a _count attribute to the select as a counter to record how many instances have hidden the select.
If the current instance determines that the select should be hidden, add 1 to its _count, and store it in the _selects collection of the instance after hiding it.
When restoring the display of the select in _selects, first decrement the _count of the select by 1. If the _count obtained is 0, it means that there are no other instances to hide it, then you can set the display, and finally clear the _selects collection .
Before judging whether to hide the select, you must restore the select in _selects of the instance, otherwise it will cause the _count to only increase but not decrease.

The SetSelect method in the program is used to determine and set the select:
Copy the code The code is as follows:

this.ResetSelect();
var rect = this._nTable.getBoundingClientRect();
//Put the ones that need to be hidden into the _selects collection
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);//Prevent multiple instance conflicts
//Set to hide
var visi = o.style. visibility;
if(visi != "hidden"){ o._css = visi; o.style.visibility = "hidden"; }

return true;
}
}) )

The ResetSelect method is used to restore the display select:

Copy code The code is as follows:

forEach(this._selects, function(o){ !--o._count && (o.style.visibility = o._css); });
this. _selects = [];

However, this method is still ineffective when scrolling quickly, and the efficiency decreases with more selections. If you have a better method, please share it.

[A bug in Chrome]

I found a bug in Chrome during testing, and tested the following code:

[Ctrl+A 全选 注:如需引入外部Js需刷新才能执行]

一个毫不相干的操作居然令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方法,其参数是要定位的位置百分比。

具体使用请参考实例。


Program source code


[Ctrl A 모두 선택 참고: 외부 J를 도입해야 하는 경우 실행하려면 새로 고쳐야 합니다 ]

완전한 테스트 코드 다운로드
재인쇄 시 출처를 표기해주세요: http://www.cnblogs.com/cloudgamer/
Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn