有固定标题的HTML表格?

是否有一个跨浏览器的CSS / JavaScript技术来显示一个长的HTML表格,使列标题保持固定在屏幕上,不滚动表体。 想一想Microsoft Excel中的“冻结窗格”效果。

我希望能够浏览表格的内容,但始终能够看到顶部的列标题。

我正在寻找一个这样的解决scheme一段时间,发现大多数的答案不工作或不适合我的情况,所以我写了一个简单的解决scheme与jQuery。

这是解决scheme的大纲。

  1. 克隆需要有固定标题的表格,并将克隆的副本放在原始的顶部
  2. 从桌子上取下桌子
  3. 从底部表中删除表头
  4. 调整列宽。 (我们记得原来的列宽)

下面是代码。 这里是演示固定头文件演示

<head> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"> </script> <script> function scrolify(tblAsJQueryObject, height){ var oTbl = tblAsJQueryObject; // for very large tables you can remove the four lines below // and wrap the table with <div> in the mark-up and assign // height and overflow property var oTblDiv = $("<div/>"); oTblDiv.css('height', height); oTblDiv.css('overflow','scroll'); oTbl.wrap(oTblDiv); // save original width oTbl.attr("data-item-original-width", oTbl.width()); oTbl.find('thead tr td').each(function(){ $(this).attr("data-item-original-width",$(this).width()); }); oTbl.find('tbody tr:eq(0) td').each(function(){ $(this).attr("data-item-original-width",$(this).width()); }); // clone the original table var newTbl = oTbl.clone(); // remove table header from original table oTbl.find('thead tr').remove(); // remove table body from new table newTbl.find('tbody tr').remove(); oTbl.parent().parent().prepend(newTbl); newTbl.wrap("<div/>"); // replace ORIGINAL COLUMN width newTbl.width(newTbl.attr('data-item-original-width')); newTbl.find('thead tr td').each(function(){ $(this).width($(this).attr("data-item-original-width")); }); oTbl.width(oTbl.attr('data-item-original-width')); oTbl.find('tbody tr:eq(0) td').each(function(){ $(this).width($(this).attr("data-item-original-width")); }); } $(document).ready(function(){ scrolify($('#tblNeedsScrolling'), 160); // 160 is height }); </script> </head> <body> <div style="width:300px;border:6px green solid;"> <table border="1" width="100%" id="tblNeedsScrolling"> <thead> <tr><th>Header 1</th><th>Header 2</th></tr> </thead> <tbody> <tr><td>row 1, cell 1</td><td>row 1, cell 2</td></tr> <tr><td>row 2, cell 1</td><td>row 2, cell 2</td></tr> <tr><td>row 3, cell 1</td><td>row 3, cell 2</td></tr> <tr><td>row 4, cell 1</td><td>row 4, cell 2</td></tr> <tr><td>row 5, cell 1</td><td>row 5, cell 2</td></tr> <tr><td>row 6, cell 1</td><td>row 6, cell 2</td></tr> <tr><td>row 7, cell 1</td><td>row 7, cell 2</td></tr> <tr><td>row 8, cell 1</td><td>row 8, cell 2</td></tr> </tbody> </table> </div> </body> 

这个解决scheme工作在铬&即。 因为这是基于jQuery的这应该在其他jQuery支持的浏览器以及工作。

这可以用四行代码干净地解决。

如果您只关心现代浏览器,那么通过使用CSS转换可以使固定标题变得更容易。 听起来很奇怪,但很好用:

  • HTML和CSS保持原样。
  • 没有外部的JS依赖。
  • 4行代码。
  • 适用于所有configuration(表格布局:固定等)。
 document.getElementById("wrap").addEventListener("scroll", function(){ var translate = "translate(0,"+this.scrollTop+"px)"; this.querySelector("thead").style.transform = translate; }); 

除IE8之外,对CSS转换的支持是广泛可用的 。 一些浏览器仍然需要供应商前缀。 以下是供参考的完整示例:

 document.getElementById("wrap").addEventListener("scroll",function(){ var translate = "translate(0,"+this.scrollTop+"px)"; this.querySelector("thead").style.transform = translate; }); 
 /* your existing container */ #wrap { overflow: auto; height: 400px; } /* css for demo */ td { background-color: green; width: 200px; height: 100px; } 
 <div id="wrap"> <table> <thead> <tr> <th>Foo</th> <th>Bar</th> </tr> </thead> <tbody> <tr><td></td><td></td></tr> <tr><td></td><td></td></tr> <tr><td></td><td></td></tr> <tr><td></td><td></td></tr> <tr><td></td><td></td></tr> <tr><td></td><td></td></tr> <tr><td></td><td></td></tr> <tr><td></td><td></td></tr> <tr><td></td><td></td></tr> <tr><td></td><td></td></tr> <tr><td></td><td></td></tr> <tr><td></td><td></td></tr> </tbody> </table> </div> 

我刚刚完成了一个jQuery插件,将采用有效的单表使用有效的HTML(必须有一个thead和tbody),并将输出一个固定的表头,可选的固定页脚,可以是一个克隆的头或任何你select的内容(分页等)。 如果你想利用更大的显示器,当浏览器resize时,它也将调整表的大小。 另一个附加function是能够侧滚如果表列不能全部在视图中。

http://fixedheadertable.com/

在github上: http : //markmalek.github.com/Fixed-Header-Table/

这是非常容易设置,你可以创build自己的自定义样式。 它也在所有浏览器中使用圆angular。 请记住,我刚刚发布了它,所以它仍然是技术上的testing版,而且我正在消除的小问题很less。

它适用于Internet Explorer 7,Internet Explorer 8,Safari,Firefox和Chrome。

TL; DR

如果你的目标是现代浏览器,并没有奢侈的造型需求: http : //jsfiddle.net/dPixie/byB9d/3/ …虽然四大版本是非常甜蜜的,这个版本处理stream体的宽度好很多。

大家好消息!

随着HTML5和CSS3的发展,现在至less在现代浏览器中是可行的。 我想到的稍微恶意的实现可以在这里find: http : //jsfiddle.net/dPixie/byB9d/3/ 。 我已经在FX 25,Chrome 31和IE 10中testing过…

相关的HTML(尽pipe在文档的顶部插入HTML5文档):

 <section class="positioned"> <div class="container"> <table> <thead> <tr class="header"> <th> Table attribute name <div>Table attribute name</div> </th> <th> Value <div>Value</div> </th> <th> Description <div>Description</div> </th> </tr> </thead> <tbody> <tr> <td>align</td> <td>left, center, right</td> <td>Not supported in HTML5. Deprecated in HTML 4.01. Specifies the alignment of a table according to surrounding text</td> </tr> <tr> <td>bgcolor</td> <td>rgb(x,x,x), #xxxxxx, colorname</td> <td>Not supported in HTML5. Deprecated in HTML 4.01. Specifies the background color for a table</td> </tr> <tr> <td>border</td> <td>1,""</td> <td>Specifies whether the table cells should have borders or not</td> </tr> <tr> <td>cellpadding</td> <td>pixels</td> <td>Not supported in HTML5. Specifies the space between the cell wall and the cell content</td> </tr> <tr> <td>cellspacing</td> <td>pixels</td> <td>Not supported in HTML5. Specifies the space between cells</td> </tr> <tr> <td>frame</td> <td>void, above, below, hsides, lhs, rhs, vsides, box, border</td> <td>Not supported in HTML5. Specifies which parts of the outside borders that should be visible</td> </tr> <tr> <td>rules</td> <td>none, groups, rows, cols, all</td> <td>Not supported in HTML5. Specifies which parts of the inside borders that should be visible</td> </tr> <tr> <td>summary</td> <td>text</td> <td>Not supported in HTML5. Specifies a summary of the content of a table</td> </tr> <tr> <td>width</td> <td>pixels, %</td> <td>Not supported in HTML5. Specifies the width of a table</td> </tr> </tbody> </table> </div> </section> 

有了这个CSS:

 html, body{ margin:0; padding:0; height:100%; } section { position: relative; border: 1px solid #000; padding-top: 37px; background: #500; } section.positioned { position: absolute; top:100px; left:100px; width:800px; box-shadow: 0 0 15px #333; } .container { overflow-y: auto; height: 200px; } table { border-spacing: 0; width:100%; } td + td { border-left:1px solid #eee; } td, th { border-bottom:1px solid #eee; background: #ddd; color: #000; padding: 10px 25px; } th { height: 0; line-height: 0; padding-top: 0; padding-bottom: 0; color: transparent; border: none; white-space: nowrap; } th div{ position: absolute; background: transparent; color: #fff; padding: 9px 25px; top: 0; margin-left: -25px; line-height: normal; border-left: 1px solid #800; } th:first-child div{ border: none; } 

但是怎么样?

简单地说,你有一个表头,你可以通过使其高于0px来视觉隐藏,也包含用作固定头的div。 表格的容器在顶部留下足够的空间以允许绝对定位的页眉,并且具有滚动条的表格按照您的预期显示。

上面的代码使用定位类绝对定位表(我在popup式对话框中使用它),但是您也可以在文档stream中使用它,也可以通过从容器中删除positioned类来使用它。

但是…

这并不完美。 Firefox拒绝使标题行0px(至less我没有find任何方法),但固执地保持在最低4px …这不是一个大问题,但取决于你的风格它会混乱你的边界等。

该表还使用了一种人造的方法,其中容器本身的背景颜色被用作标题div的背景,这是透明的。

概要

总而言之,根据您的要求可能会有造型问题,特别是边框或复杂的背景。 也可能有可计算性的问题,我还没有在各种各样的浏览器中检查过(如果你试试看,请评论你的经验),但是我没有find任何这样的东西,所以我认为这是值得发布反正…

所有从CSS规范之外解决这个问题的尝试都是我们真正想要的东西的苍白阴影:交付对THEAD的暗示的承诺。

这个冻结表头问题在HTML / CSS中已经有很长一段时间了。

在一个完美的世界里,这个问题将会有一个纯粹的css解决scheme。 不幸的是,似乎没有一个好的地方。

相关标准 – 关于这个话题的讨论包括:

  • 以www风格的粘滞定位build议: http : //lists.w3.org/Archives/Public/www-style/2012Jun/0627.html
  • Atkins对位置根,位置包含或位置限制的build议: http : //www.xanthir.com/blog/b48H0

更新 :Firefox运输位置:在版本32粘。每个人都赢了!

我也创build了一个解决这个问题的插件。 我的项目 – jQuery.floatThead已经存在了一年多了,已经非常成熟了。

它不需要外部样式,也不希望你的桌子以任何特定的方式被devise。 它支持IE8 +和FF / Chrome。

目前它有:

github上有231个提交和393个星星


这里的很多(不是全部)答案都是可以解决一个人遇到的问题的快速黑客,但是不会为每个桌子工作。

一些其他的插件是旧的,可​​能与IE浏览器很好,但会打破FF和铬。

这是一个固定表头的jQuery插件。 它允许整个页面滚动,冻结标题,当它到达顶部。 它与twitter引导表很好地工作。

Github回购: https : //github.com/oma/table-fixed-header

它不会只滚动表格内容。 作为其他答案之一,请查看其他工具。 你决定最适合你的情况。

这里发布的大多数解决scheme都需要jQuery。 如果您正在寻找一个独立于框架的解决scheme,请尝试Grid: http : //www.matts411.com/post/grid/

它在Github上托pipe在这里: https : //github.com/mmurph211/Grid

它不仅支持固定标题,还支持固定的左列和右脚,等等。

一个简单的jQuery插件

这是Mahes解决scheme的一个变种。 你可以把它叫做$('table#foo').scrollableTable();

这个想法是:

  • theadtbody拆分成单独的table元素
  • 使他们的单元格宽度再次匹配
  • 将第二个table div.scrollable
  • 使用CSS使div.scrollable实际上滚动

CSS可以是:

 div.scrollable { height: 300px; overflow-y: scroll;} 

注意事项

  • 很显然,分解这些表使得标记语义更less。 我不确定这对可访问性有什么影响。
  • 这个插件不处理页脚,多个标题等
  • 我只在Chrome版本20中testing过。

这就是说,它适用于我的目的,你可以自由地采取和修改它。

这是插件:

 jQuery.fn.scrollableTable = function () { var $newTable, $oldTable, $scrollableDiv, originalWidths; $oldTable = $(this); // Once the tables are split, their cell widths may change. // Grab these so we can make the two tables match again. originalWidths = $oldTable.find('tr:first td').map(function() { return $(this).width(); }); $newTable = $oldTable.clone(); $oldTable.find('tbody').remove(); $newTable.find('thead').remove(); $.each([$oldTable, $newTable], function(index, $table) { $table.find('tr:first td').each(function(i) { $(this).width(originalWidths[i]); }); }); $scrollableDiv = $('<div/>').addClass('scrollable'); $newTable.insertAfter($oldTable).wrap($scrollableDiv); }; 

更精致的纯CSS滚动表

到目前为止,我所见过的所有纯粹的CSS解决scheme – 尽pipe它们可能是聪明的,但缺乏一定程度的抛光,或者在某些情况下无法正常工作。 所以,我决定创build自己的…

特征:

  • 它是纯粹的CSS,所以不需要jQuery(或者任何的JavaScript)
  • 您可以将表格宽度设置为百分比(又名“stream体”)或固定值,或者让内容决定其宽度(又名“自动”)
  • 色谱柱宽度也可以是stream体,固定或自动。
  • 由于水平滚动,列将永远不会与标题错位(每个其他基于CSS的解决scheme中发生的问题都不需要固定的宽度)。
  • 兼容所有stream行的桌面浏览器,包括Internet Explorer版本8
  • 干净,抛光的外观; 没有马虎般的1像素的差距或错位的边界; 在所有浏览器中看起来都一样

下面是一些显示stream体和自动宽度选项的小提琴:

  • stream体宽度和高度 (适应屏幕大小): jsFiddle (请注意,在此configuration中只有在需要时才显示滚动条,因此您可能需要缩小框架才能看到它)

  • 自动宽度,固定高度 (更容易与其他内容整合): jsFiddle

自动宽度,固定高度configuration可能有更多的用例,所以我会张贴下面的代码。

HTML

 <div class="scrollingtable"> <div> <div> <table> <caption>Top Caption</caption> <thead> <tr> <th><div label="Column 1"/></th> <th><div label="Column 2"/></th> <th><div label="Column 3"/></th> <th> <!--more versatile way of doing column label; requires 2 identical copies of label--> <div><div>Column 4</div><div>Column 4</div></div> </th> <th class="scrollbarhead"/> <!--ALWAYS ADD THIS EXTRA CELL AT END OF HEADER ROW--> </tr> </thead> <tbody> <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr> <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr> <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr> <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr> <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr> <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr> <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr> <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr> <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr> <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr> <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr> <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr> </tbody> </table> </div> Faux bottom caption </div> </div> 

CSS

 <!--[if lte IE 9]><style>.scrollingtable > div > div > table {margin-right: 17px;}</style><![endif]--> <style> /*the following html and body rule sets are required only if using a % width or height*/ /*html { width: 100%; height: 100%; }*/ body { box-sizing: border-box; width: 100%; height: 100%; margin: 0; padding: 0 20px 0 20px; text-align: center; } .scrollingtable { box-sizing: border-box; display: inline-block; vertical-align: middle; overflow: hidden; width: auto; /*if you want a fixed width, set it here, else set to auto*/ min-width: 0/*100%*/; /*if you want a % width, set it here, else set to 0*/ height: 188px/*100%*/; /*set table height here; can be fixed value or %*/ min-height: 0/*104px*/; /*if using % height, make this large enough to fit scrollbar arrows + caption + thead*/ font-family: Verdana, Tahoma, sans-serif; font-size: 16px; line-height: 20px; padding: 20px 0 20px 0; /*need enough padding to make room for caption*/ text-align: left; } .scrollingtable * {box-sizing: border-box;} .scrollingtable > div { position: relative; border-top: 1px solid black; height: 100%; padding-top: 20px; /*this determines column header height*/ } .scrollingtable > div:before { top: 0; background: cornflowerblue; /*header row background color*/ } .scrollingtable > div:before, .scrollingtable > div > div:after { content: ""; position: absolute; z-index: -1; width: 100%; height: 100%; left: 0; } .scrollingtable > div > div { min-height: 0/*43px*/; /*if using % height, make this large enough to fit scrollbar arrows*/ max-height: 100%; overflow: scroll/*auto*/; /*set to auto if using fixed or % width; else scroll*/ overflow-x: hidden; border: 1px solid black; /*border around table body*/ } .scrollingtable > div > div:after {background: white;} /*match page background color*/ .scrollingtable > div > div > table { width: 100%; border-spacing: 0; margin-top: -20px; /*inverse of column header height*/ /*margin-right: 17px;*/ /*uncomment if using % width*/ } .scrollingtable > div > div > table > caption { position: absolute; top: -20px; /*inverse of caption height*/ margin-top: -1px; /*inverse of border-width*/ width: 100%; font-weight: bold; text-align: center; } .scrollingtable > div > div > table > * > tr > * {padding: 0;} .scrollingtable > div > div > table > thead { vertical-align: bottom; white-space: nowrap; text-align: center; } .scrollingtable > div > div > table > thead > tr > * > div { display: inline-block; padding: 0 6px 0 6px; /*header cell padding*/ } .scrollingtable > div > div > table > thead > tr > :first-child:before { content: ""; position: absolute; top: 0; left: 0; height: 20px; /*match column header height*/ border-left: 1px solid black; /*leftmost header border*/ } .scrollingtable > div > div > table > thead > tr > * > div[label]:before, .scrollingtable > div > div > table > thead > tr > * > div > div:first-child, .scrollingtable > div > div > table > thead > tr > * + :before { position: absolute; top: 0; white-space: pre-wrap; color: white; /*header row font color*/ } .scrollingtable > div > div > table > thead > tr > * > div[label]:before, .scrollingtable > div > div > table > thead > tr > * > div[label]:after {content: attr(label);} .scrollingtable > div > div > table > thead > tr > * + :before { content: ""; display: block; min-height: 20px; /*match column header height*/ padding-top: 1px; border-left: 1px solid black; /*borders between header cells*/ } .scrollingtable .scrollbarhead {float: right;} .scrollingtable .scrollbarhead:before { position: absolute; width: 100px; top: -1px; /*inverse border-width*/ background: white; /*match page background color*/ } .scrollingtable > div > div > table > tbody > tr:after { content: ""; display: table-cell; position: relative; padding: 0; border-top: 1px solid black; top: -1px; /*inverse of border width*/ } .scrollingtable > div > div > table > tbody {vertical-align: top;} .scrollingtable > div > div > table > tbody > tr {background: white;} .scrollingtable > div > div > table > tbody > tr > * { border-bottom: 1px solid black; padding: 0 6px 0 6px; height: 20px; /*match column header height*/ } .scrollingtable > div > div > table > tbody:last-of-type > tr:last-child > * {border-bottom: none;} .scrollingtable > div > div > table > tbody > tr:nth-child(even) {background: gainsboro;} /*alternate row color*/ .scrollingtable > div > div > table > tbody > tr > * + * {border-left: 1px solid black;} /*borders between body cells*/ </style> 

我用来冻结标题行的方法与d-Pixie类似,所以请参考他的post以获得解释。 这种技术存在一些bug和限制,只能用一堆额外的CSS和一个额外的div容器来修复。

支持固定页脚

我扩展了Nathan的function,也支持固定页脚和最大高度。 此外,该function将设置CSS本身,你只需要支持一个宽度。

用法:

固定高度:

 $('table').scrollableTable({ height: 100 }); 

最大高度(如果浏览器支持css'max-height'选项):

 $('table').scrollableTable({ maxHeight: 100 }); 

脚本:

 jQuery.fn.scrollableTable = function(options) { var $originalTable, $headTable, $bodyTable, $footTable, $scrollableDiv, originalWidths; // prepare the separate parts of the table $originalTable = $(this); $headTable = $originalTable.clone(); $headTable.find('tbody').remove(); $headTable.find('tfoot').remove(); $bodyTable = $originalTable.clone(); $bodyTable.find('thead').remove(); $bodyTable.find('tfoot').remove(); $footTable = $originalTable.clone(); $footTable.find('thead').remove(); $footTable.find('tbody').remove(); // grap original column widths and set them in the separate tables originalWidths = $originalTable.find('tr:first td').map(function() { return $(this).width(); }); $.each([$headTable, $bodyTable, $footTable], function(index, $table) { $table.find('tr:first td').each(function(i) { $(this).width(originalWidths[i]); }); }); // the div that makes the body table scroll $scrollableDiv = $('<div/>').css({ 'overflow-y': 'scroll' }); if(options.height) { $scrollableDiv.css({'height': options.height}); } else if(options.maxHeight) { $scrollableDiv.css({'max-height': options.maxHeight}); } // add the new separate tables and remove the original one $headTable.insertAfter($originalTable); $bodyTable.insertAfter($headTable); $footTable.insertAfter($bodyTable); $bodyTable.wrap($scrollableDiv); $originalTable.remove(); }; 

🙂

不是很干净,但纯粹的HTML / CSS解决scheme。

 table { overflow-x:scroll; } tbody { max-height: /*your desired max height*/ overflow-y:scroll; display:block; } 

更新了IE8 + JSFiddle示例

两个div,一个用于标题,一个用于数据。 使数据div可滚动,并使用JavaScript将标题中列的宽度设置为与数据中的宽度相同。 我认为数据列的宽度需要固定而不是dynamic的。

我意识到这个问题允许JavaScript,但这里是一个纯粹的CSS解决scheme,我也研究过,也允许表横向扩展。 testingIE10和最新的Chrome和Firefox浏览器。 链接到jsFiddle是在底部。

HTML:

 Putting some text here to differentiate between the header aligning with the top of the screen and the header aligning with the top of one of it's ancestor containers. <div id="positioning-container"> <div id="scroll-container"> <table> <colgroup> <col class="col1"></col> <col class="col2"></col> </colgroup> <thead> <th class="header-col1"><div>Header 1</div></th> <th class="header-col2"><div>Header 2</div></th> </thead> <tbody> <tr><td>Cell 1.1</td><td>Cell 1.2</td></tr> <tr><td>Cell 2.1</td><td>Cell 2.2</td></tr> <tr><td>Cell 3.1</td><td>Cell 3.2</td></tr> <tr><td>Cell 4.1</td><td>Cell 4.2</td></tr> <tr><td>Cell 5.1</td><td>Cell 5.2</td></tr> <tr><td>Cell 6.1</td><td>Cell 6.2</td></tr> <tr><td>Cell 7.1</td><td>Cell 7.2</td></tr> </tbody> </table> </div> </div> 

而CSS:

 table{ border-collapse: collapse; table-layout: fixed; width: 100%; } /* Not required, just helps with alignment for this example */ td, th{ padding: 0; margin: 0; } tbody{ background-color: #ddf; } thead { /* Keeps the header in place. Don't forget top: 0 */ position: absolute; top: 0; background-color: #ddd; /* The 17px is to adjust for the scrollbar width. * This is a new css value that makes this pure * css example possible */ width: calc(100% - 17px); height: 20px; } /* Positioning container. Required to position the * header since the header uses position:absolute * (otherwise it would position at the top of the screen) */ #positioning-container{ position: relative; } /* A container to set the scroll-bar and * includes padding to move the table contents * down below the header (padding = header height) */ #scroll-container{ overflow-y: auto; padding-top: 20px; height: 100px; } .header-col1{ background-color: red; } /* fixed width header columns need a div to set their width */ .header-col1 div{ width: 100px; } /* expandable columns need a width set on the th tag */ .header-col2{ width: 100%; } .col1 { width: 100px; } .col2{ width: 100%; } 

http://jsfiddle.net/HNHRv/3/

使用最新版本的jQuery,并包含以下JavaScript代码

 $(window).scroll(function(){ $("id of the div element").offset({top:$(window).scrollTop()}); }); 

这不是固定标题行的确切解决scheme,但是我已经创build了一个非常巧妙的方法来在整个长表中重复标题行,但仍然保持sorting的能力。 这个整洁的小选项需要jQuery tablesorter插件 。 这是如何工作的:

HTML

 <table class="tablesorter boxlist" id="pmtable"> <thead class="fixedheader"> <tr class="boxheadrow"> <th width="70px" class="header">Job Number</th> <th width="10px" class="header">Pri</th> <th width="70px" class="header">CLLI</th> <th width="35px" class="header">Market</th> <th width="35px" class="header">Job Status</th> <th width="65px" class="header">Technology</th> <th width="95px;" class="header headerSortDown">MEI</th> <th width="95px" class="header">TEO Writer</th> <th width="75px" class="header">Quote Due</th> <th width="100px" class="header">Engineer</th> <th width="75px" class="header">ML Due</th> <th width="75px" class="header">ML Complete</th> <th width="75px" class="header">SPEC Due</th> <th width="75px" class="header">SPEC Complete</th> <th width="100px" class="header">Install Supervisor</th> <th width="75px" class="header">MasTec OJD</th> <th width="75px" class="header">Install Start</th> <th width="30px" class="header">Install Hours</th> <th width="75px" class="header">Revised CRCD</th> <th width="75px" class="header">Latest Ship-To-Site</th> <th width="30px" class="header">Total Parts</th> <th width="30px" class="header">OEM Rcvd</th> <th width="30px" class="header">Minor Rcvd</th> <th width="30px" class="header">Total Received</th> <th width="30px" class="header">% On Site</th> <th width="60px" class="header">Actions</th> </tr> </thead> <tbody class="scrollable"> <tr data-job_id="3548" data-ml_id="" class="odd"> <td class="c black">FL-8-RG9UP</td> <td data-pri="2" class="priority c yellow">M</td> <td class="c">FTLDFLOV</td> <td class="c">SFL</td> <td class="c">NOI</td> <td class="c">TRANSPORT</td> <td class="c"></td> <td class="c">Chris Byrd</td> <td class="c">Apr 13, 2013</td> <td class="c">Kris Hall</td> <td class="c">May 20, 2013</td> <td class="c">May 20, 2013</td> <td class="c">Jun 5, 2013</td> <td class="c">Jun 7, 2013</td> <td class="c">Joseph Fitz</td> <td class="c">Jun 10, 2013</td> <td class="c">TBD</td> <td class="c">123</td> <td class="c revised_crcd"><input readonly="true" name="revised_crcd" value="Jul 26, 2013" type="text" size="12" class="smInput r_crcd c hasDatepicker" id="dp1377194058616"></td> <td class="c">TBD</td> <td class="c">N/A</td> <td class="c">N/A</td> <td class="c">N/A</td> <td class="c">N/A</td> <td class="c">N/A</td> <td class="actions"><span style="float:left;" class="ui-icon ui-icon-folder-open editJob" title="View this job" s="" details'=""></span></td> </tr> <tr data-job_id="4264" data-ml_id="2959" class="even"> <td class="c black">MTS13009SF</td> <td data-pri="2" class="priority c yellow">M</td> <td class="c">OJUSFLTL</td> <td class="c">SFL</td> <td class="c">NOI</td> <td class="c">TRANSPORT</td> <td class="c"></td> <td class="c">DeMarcus Stewart</td> <td class="c">May 22, 2013</td> <td class="c">Ryan Alsobrook</td> <td class="c">Jun 19, 2013</td> <td class="c">Jun 27, 2013</td> <td class="c">Jun 19, 2013</td> <td class="c">Jul 4, 2013</td> <td class="c">Randy Williams</td> <td class="c">Jun 21, 2013</td> <td class="c">TBD</td> <td class="c">95</td> <td class="c revised_crcd"><input readonly="true" name="revised_crcd" value="Aug 9, 2013" type="text" size="12" class="smInput r_crcd c hasDatepicker" id="dp1377194058632"></td><td class="c">TBD</td> <td class="c">0</td> <td class="c">0.00%</td> <td class="c">0.00%</td> <td class="c">0.00%</td> <td class="c">0.00%</td> <td class="actions"><span style="float:left;" class="ui-icon ui-icon-folder-open editJob" title="View this job" s="" details'=""></span><input style="float:left;" type="hidden" name="req_ship" class="reqShip hasDatepicker" id="dp1377194058464"><span style="float:left;" class="ui-icon ui-icon-calendar requestShip" title="Schedule this job for shipping"></span><span class="ui-icon ui-icon-info viewOrderInfo" style="float:left;" title="Show material details for this order"></span></td> </tr> . . . . <tr class="boxheadrow repeated-header"> <th width="70px" class="header">Job Number</th> <th width="10px" class="header">Pri</th> <th width="70px" class="header">CLLI</th> <th width="35px" class="header">Market</th> <th width="35px" class="header">Job Status</th> <th width="65px" class="header">Technology</th> <th width="95px;" class="header">MEI</th> <th width="95px" class="header">TEO Writer</th> <th width="75px" class="header">Quote Due</th> <th width="100px" class="header">Engineer</th> <th width="75px" class="header">ML Due</th> <th width="75px" class="header">ML Complete</th> <th width="75px" class="header">SPEC Due</th> <th width="75px" class="header">SPEC Complete</th> <th width="100px" class="header">Install Supervisor</th> <th width="75px" class="header">MasTec OJD</th> <th width="75px" class="header">Install Start</th> <th width="30px" class="header">Install Hours</th> <th width="75px" class="header">Revised CRCD</th> <th width="75px" class="header">Latest Ship-To-Site</th> <th width="30px" class="header">Total Parts</th> <th width="30px" class="header">OEM Rcvd</th> <th width="30px" class="header">Minor Rcvd</th> <th width="30px" class="header">Total Received</th> <th width="30px" class="header">% On Site</th> <th width="60px" class="header">Actions</th> </tr> 

Obvioiusly, my table has many more rows than this. 193 to be exact, nut you can see where the header row repeats. The repeating header row is setup by this function:

jQuery的

  // clone the original header row and add the "repeated-header" class var tblHeader = $('tr.boxheadrow').clone().addClass('repeated-header'); // add the cloned header with the new class every 34th row (or as you see fit) $('tbody tr:odd:nth-of-type(17n)').after(tblHeader); // on the 'sortStart' routine, remove all the inserted header rows $('#pmtable').bind('sortStart', function() { $('.repeated-header').remove(); // on the 'sortEnd' routine, add back all the header row lines. }).bind('sortEnd', function() { $('tbody tr:odd:nth-of-type(17n)').after(tblHeader); }); 

I wish I had found @Mark's solution earlier, but I went and wrote my own before I saw this SO question…

Mine is a very lightweight jQuery plugin that supports fixed header, footer, column spanning (colspan), resizing, horizontal scrolling, and an optional number of rows to display before scrolling starts.

jQuery.scrollTableBody (GitHub)

As long as you have a table with proper <thead> , <tbody> , and (optional) <tfoot> , all you need to do is this:

 $('table').scrollTableBody(); 

I developed a simple light weight jQuery plug-in for converting a well HTML table to a scrollable table with fixed table header and columns.

The plugin works well to match pixel-to-pixel positioning the fixed section with the scrollable section. Additionally, you could also freeze number of columns that will be always in view when scrolling horizontally.

Demo & Documentation: http://meetselva.github.io/fixed-table-rows-cols/

Github Repo: https://github.com/meetselva/fixed-table-rows-cols

Below is the usage for a simple table with fixed header,

 $(<table selector>).fxdHdrCol({ width: "100%", height: 200, colModal: [{width: 30, align: 'center'}, {width: 70, align: 'center'}, {width: 200, align: 'left'}, {width: 100, align: 'center'}, {width: 70, align: 'center'}, {width: 250, align: 'center'} ] }); 

A lot of people seem to be looking for this answer, I found it buried in an answer to another question here: Syncing column width of between tables in two different frames, etc

Of the dozens of methods I have tried this is the only method I found that works reliably to allow you to have a scrolling bottom table with the header table having the same widths.

Here is how I did it, first I improved upon the jsfiddle above to create this function, which works on both td and th (in case that trips up others who use th for styling of their header rows).

 var setHeaderTableWidth= function (headertableid,basetableid) { $("#"+headertableid).width($("#"+basetableid).width()); $("#"+headertableid+" tr th").each(function (i) { $(this).width($($("#"+basetableid+" tr:first td")[i]).width()); }); $("#" + headertableid + " tr td").each(function (i) { $(this).width($($("#" + basetableid + " tr:first td")[i]).width()); }); } 

Next, you need to create two tables, NOTE the header table should have an extra TD to leave room in the top table for the scrollbar, like this:

  <table id="headertable1" class="input-cells table-striped"> <thead> <tr style="background-color:darkgray;color:white;"><th>header1</th><th>header2</th><th>header3</th><th>header4</th><th>header5</th><th>header6</th><th></th></tr> </thead> </table> <div id="resizeToBottom" style="overflow-y:scroll;overflow-x:hidden;"> <table id="basetable1" class="input-cells table-striped"> <tbody > <tr> <td>testdata</td> <td>2</td> <td>3</td> <td>4</span></td> <td>55555555555555</td> <td>test</td></tr> </tbody> </table> </div> 

then do something like:

  setHeaderTableWidth('headertable1', 'basetable1'); $(window).resize(function () { setHeaderTableWidth('headertable1', 'basetable1'); }); 

This is the only solution that I found on stackoverflow that works out of many similar questions that have been posted, that works in all my cases.

For example I tried the jquery stickytables plugin which does not work with durandal, and the google code project here https://code.google.com/p/js-scroll-table-header/issues/detail?id=2

Other solutions involving cloning the tables, have poor performance, or suck and don't work in all cases.

THERE IS NO NEED FOR THESE OVERLY COMPLEX SOLUTIONS, JUST MAKE TWO TABLES LIKE THE EXAMPLES BELOW AND CALL setHeaderTableWidth function like described here and BOOM, YOU ARE DONE.

If this does not work for you, you probably were playing with your css box-sizing property and you need to set it correctly. It is easy to screw up your css by accident there are many things that can go wrong so just be aware/careful of that. THIS APPROACH WORKS FOR ME. I'm curious if it works for others, let me know. 祝你好运!

Here's a solution that we ended up working with (in order to deal with some edge cases and older versions of IE we eventually also faded out the title bar on scroll then fade it back in when scrolling ends, but in Firefox and Webkit browsers this solution just works . It assumes border-collapse: collapse.

The key to this solution is that once you apply border-collapse , CSS transforms work on the header, so it's just a matter of intercepting scroll events and setting the transform correctly. You don't need to duplicate anything. Short of this behavior being implemented properly in the browser, it's hard to imagine a more light-weight solution.

JSFiddle: http://jsfiddle.net/podperson/tH9VU/2/

It's implemented as a simple jQuery plugin. You simply make your thead's sticky with a call like $('thead').sticky() and they'll hang around. Works for multiple tables on a page and head sections halfway down big tables.

  $.fn.sticky = function(){ $(this).each( function(){ var thead = $(this), tbody = thead.next('tbody'); updateHeaderPosition(); function updateHeaderPosition(){ if( thead.offset().top < $(document).scrollTop() && tbody.offset().top + tbody.height() > $(document).scrollTop() ){ var tr = tbody.find('tr').last(), y = tr.offset().top - thead.height() < $(document).scrollTop() ? tr.offset().top - thead.height() - thead.offset().top : $(document).scrollTop() - thead.offset().top; thead.find('th').css({ 'z-index': 100, 'transform': 'translateY(' + y + 'px)', '-webkit-transform': 'translateY(' + y + 'px)' }); } else { thead.find('th').css({ 'transform': 'none', '-webkit-transform': 'none' }); } } // see http://www.quirksmode.org/dom/events/scroll.html $(window).on('scroll', updateHeaderPosition); }); } $('thead').sticky(); 

I found this workaround – move header row in a table above table with data:

 <html> <head> <title>Fixed header</title> <style> table td {width:75px;} </style> </head> <body> <div style="height:auto; width:350px; overflow:auto"> <table border="1"> <tr> <td>header 1</td> <td>header 2</td> <td>header 3</td> </tr> </table> </div> <div style="height:50px; width:350px; overflow:auto"> <table border="1"> <tr> <td>row 1 col 1</td> <td>row 1 col 2</td> <td>row 1 col 3</td> </tr> <tr> <td>row 2 col 1</td> <td>row 2 col 2</td> <td>row 2 col 3</td> </tr> <tr> <td>row 3 col 1</td> <td>row 3 col 2</td> <td>row 3 col 3</td> </tr> <tr> <td>row 4 col 1</td> <td>row 4 col 2</td> <td>row 4 col 3</td> </tr> <tr> <td>row 5 col 1</td> <td>row 5 col 2</td> <td>row 5 col 3</td> </tr> <tr> <td>row 6 col 1</td> <td>row 6 col 2</td> <td>row 6 col 3</td> </tr> </table> </div> </body> </html> 

By applying the StickyTableHeaders jQuery plugin to the table, the column headers will stick to the top of the viewport as you scroll down.

例:

 $(function () { $("table").stickyTableHeaders(); }); /*! Copyright (c) 2011 by Jonas Mosbech - https://github.com/jmosbech/StickyTableHeaders MIT license info: https://github.com/jmosbech/StickyTableHeaders/blob/master/license.txt */ ; (function ($, window, undefined) { 'use strict'; var name = 'stickyTableHeaders', id = 0, defaults = { fixedOffset: 0, leftOffset: 0, marginTop: 0, scrollableArea: window }; function Plugin(el, options) { // To avoid scope issues, use 'base' instead of 'this' // to reference this class from internal events and functions. var base = this; // Access to jQuery and DOM versions of element base.$el = $(el); base.el = el; base.id = id++; base.$window = $(window); base.$document = $(document); // Listen for destroyed, call teardown base.$el.bind('destroyed', $.proxy(base.teardown, base)); // Cache DOM refs for performance reasons base.$clonedHeader = null; base.$originalHeader = null; // Keep track of state base.isSticky = false; base.hasBeenSticky = false; base.leftOffset = null; base.topOffset = null; base.init = function () { base.$el.each(function () { var $this = $(this); // remove padding on <table> to fix issue #7 $this.css('padding', 0); base.$originalHeader = $('thead:first', this); base.$clonedHeader = base.$originalHeader.clone(); $this.trigger('clonedHeader.' + name, [base.$clonedHeader]); base.$clonedHeader.addClass('tableFloatingHeader'); base.$clonedHeader.css('display', 'none'); base.$originalHeader.addClass('tableFloatingHeaderOriginal'); base.$originalHeader.after(base.$clonedHeader); base.$printStyle = $('<style type="text/css" media="print">' + '.tableFloatingHeader{display:none !important;}' + '.tableFloatingHeaderOriginal{position:static !important;}' + '</style>'); $('head').append(base.$printStyle); }); base.setOptions(options); base.updateWidth(); base.toggleHeaders(); base.bind(); }; base.destroy = function () { base.$el.unbind('destroyed', base.teardown); base.teardown(); }; base.teardown = function () { if (base.isSticky) { base.$originalHeader.css('position', 'static'); } $.removeData(base.el, 'plugin_' + name); base.unbind(); base.$clonedHeader.remove(); base.$originalHeader.removeClass('tableFloatingHeaderOriginal'); base.$originalHeader.css('visibility', 'visible'); base.$printStyle.remove(); base.el = null; base.$el = null; }; base.bind = function () { base.$scrollableArea.on('scroll.' + name, base.toggleHeaders); if (!base.isWindowScrolling) { base.$window.on('scroll.' + name + base.id, base.setPositionValues); base.$window.on('resize.' + name + base.id, base.toggleHeaders); } base.$scrollableArea.on('resize.' + name, base.toggleHeaders); base.$scrollableArea.on('resize.' + name, base.updateWidth); }; base.unbind = function () { // unbind window events by specifying handle so we don't remove too much base.$scrollableArea.off('.' + name, base.toggleHeaders); if (!base.isWindowScrolling) { base.$window.off('.' + name + base.id, base.setPositionValues); base.$window.off('.' + name + base.id, base.toggleHeaders); } base.$scrollableArea.off('.' + name, base.updateWidth); }; base.toggleHeaders = function () { if (base.$el) { base.$el.each(function () { var $this = $(this), newLeft, newTopOffset = base.isWindowScrolling ? ( isNaN(base.options.fixedOffset) ? base.options.fixedOffset.outerHeight() : base.options.fixedOffset) : base.$scrollableArea.offset().top + (!isNaN(base.options.fixedOffset) ? base.options.fixedOffset : 0), offset = $this.offset(), scrollTop = base.$scrollableArea.scrollTop() + newTopOffset, scrollLeft = base.$scrollableArea.scrollLeft(), scrolledPastTop = base.isWindowScrolling ? scrollTop > offset.top : newTopOffset > offset.top, notScrolledPastBottom = (base.isWindowScrolling ? scrollTop : 0) < (offset.top + $this.height() - base.$clonedHeader.height() - (base.isWindowScrolling ? 0 : newTopOffset)); if (scrolledPastTop && notScrolledPastBottom) { newLeft = offset.left - scrollLeft + base.options.leftOffset; base.$originalHeader.css({ 'position': 'fixed', 'margin-top': base.options.marginTop, 'left': newLeft, 'z-index': 3 // #18: opacity bug }); base.leftOffset = newLeft; base.topOffset = newTopOffset; base.$clonedHeader.css('display', ''); if (!base.isSticky) { base.isSticky = true; // make sure the width is correct: the user might have resized the browser while in static mode base.updateWidth(); } base.setPositionValues(); } else if (base.isSticky) { base.$originalHeader.css('position', 'static'); base.$clonedHeader.css('display', 'none'); base.isSticky = false; base.resetWidth($('td,th', base.$clonedHeader), $('td,th', base.$originalHeader)); } }); } }; base.setPositionValues = function () { var winScrollTop = base.$window.scrollTop(), winScrollLeft = base.$window.scrollLeft(); if (!base.isSticky || winScrollTop < 0 || winScrollTop + base.$window.height() > base.$document.height() || winScrollLeft < 0 || winScrollLeft + base.$window.width() > base.$document.width()) { return; } base.$originalHeader.css({ 'top': base.topOffset - (base.isWindowScrolling ? 0 : winScrollTop), 'left': base.leftOffset - (base.isWindowScrolling ? 0 : winScrollLeft) }); }; base.updateWidth = function () { if (!base.isSticky) { return; } // Copy cell widths from clone if (!base.$originalHeaderCells) { base.$originalHeaderCells = $('th,td', base.$originalHeader); } if (!base.$clonedHeaderCells) { base.$clonedHeaderCells = $('th,td', base.$clonedHeader); } var cellWidths = base.getWidth(base.$clonedHeaderCells); base.setWidth(cellWidths, base.$clonedHeaderCells, base.$originalHeaderCells); // Copy row width from whole table base.$originalHeader.css('width', base.$clonedHeader.width()); }; base.getWidth = function ($clonedHeaders) { var widths = []; $clonedHeaders.each(function (index) { var width, $this = $(this); if ($this.css('box-sizing') === 'border-box') { width = $this[0].getBoundingClientRect().width; // #39: border-box bug } else { var $origTh = $('th', base.$originalHeader); if ($origTh.css('border-collapse') === 'collapse') { if (window.getComputedStyle) { width = parseFloat(window.getComputedStyle(this, null).width); } else { // ie8 only var leftPadding = parseFloat($this.css('padding-left')); var rightPadding = parseFloat($this.css('padding-right')); // Needs more investigation - this is assuming constant border around this cell and it's neighbours. var border = parseFloat($this.css('border-width')); width = $this.outerWidth() - leftPadding - rightPadding - border; } } else { width = $this.width(); } } widths[index] = width; }); return widths; }; base.setWidth = function (widths, $clonedHeaders, $origHeaders) { $clonedHeaders.each(function (index) { var width = widths[index]; $origHeaders.eq(index).css({ 'min-width': width, 'max-width': width }); }); }; base.resetWidth = function ($clonedHeaders, $origHeaders) { $clonedHeaders.each(function (index) { var $this = $(this); $origHeaders.eq(index).css({ 'min-width': $this.css('min-width'), 'max-width': $this.css('max-width') }); }); }; base.setOptions = function (options) { base.options = $.extend({}, defaults, options); base.$scrollableArea = $(base.options.scrollableArea); base.isWindowScrolling = base.$scrollableArea[0] === window; }; base.updateOptions = function (options) { base.setOptions(options); // scrollableArea might have changed base.unbind(); base.bind(); base.updateWidth(); base.toggleHeaders(); }; // Run initializer base.init(); } // A plugin wrapper around the constructor, // preventing against multiple instantiations $.fn[name] = function (options) { return this.each(function () { var instance = $.data(this, 'plugin_' + name); if (instance) { if (typeof options === 'string') { instance[options].apply(instance); } else { instance.updateOptions(options); } } else if (options !== 'destroy') { $.data(this, 'plugin_' + name, new Plugin(this, options)); } }); }; })(jQuery, window); 
 body { margin: 0 auto; padding: 0 20px; font-family: Arial, Helvetica, sans-serif; font-size: 11px; color: #555; } table { border: 0; padding: 0; margin: 0 0 20px 0; border-collapse: collapse; } th { padding: 5px; /* NOTE: th padding must be set explicitly in order to support IE */ text-align: right; font-weight:bold; line-height: 2em; color: #FFF; background-color: #555; } tbody td { padding: 10px; line-height: 18px; border-top: 1px solid #E0E0E0; } tbody tr:nth-child(2n) { background-color: #F7F7F7; } tbody tr:hover { background-color: #EEEEEE; } td { text-align: right; } td:first-child, th:first-child { text-align: left; } 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <div style="width:3000px">some really really wide content goes here</div> <table> <thead> <tr> <th colspan="9">Companies listed on NASDAQ OMX Copenhagen.</th> </tr> <tr> <th>Full name</th> <th>CCY</th> <th>Last</th> <th>+/-</th> <th>%</th> <th>Bid</th> <th>Ask</th> <th>Volume</th> <th>Turnover</th> </tr> </thead> <tbody> <tr> <td>AP Møller...</td> <td>DKK</td> <td>33,220.00</td> <td>760</td> <td>2.34</td> <td>33,140.00</td> <td>33,220.00</td> <td>594</td> <td>19,791,910</td> </tr> <tr> <td>AP Møller...</td> <td>DKK</td> <td>34,620.00</td> <td>640</td> <td>1.88</td> <td>34,620.00</td> <td>34,700.00</td> <td>9,954</td> <td>346,530,246</td> </tr> <tr> <td>Carlsberg A</td> <td>DKK</td> <td>380</td> <td>0</td> <td>0</td> <td>371</td> <td>391.5</td> <td>6</td> <td>2,280</td> </tr> <tr> <td>Carlsberg B</td> <td>DKK</td> <td>364.4</td> <td>8.6</td> <td>2.42</td> <td>363</td> <td>364.4</td> <td>636,267</td> <td>228,530,601</td> </tr> <tr> <td>Chr. Hansen...</td> <td>DKK</td> <td>114.5</td> <td>-1.6</td> <td>-1.38</td> <td>114.2</td> <td>114.5</td> <td>141,822</td> <td>16,311,454</td> </tr> <tr> <td>Coloplast B</td> <td>DKK</td> <td>809.5</td> <td>11</td> <td>1.38</td> <td>809</td> <td>809.5</td> <td>85,840</td> <td>69,363,301</td> </tr> <tr> <td>D/S Norden</td> <td>DKK</td> <td>155</td> <td>-1.5</td> <td>-0.96</td> <td>155</td> <td>155.1</td> <td>51,681</td> <td>8,037,225</td> </tr> <tr> <td>Danske Bank</td> <td>DKK</td> <td>69.05</td> <td>2.55</td> <td>3.83</td> <td>69.05</td> <td>69.2</td> <td>1,723,719</td> <td>115,348,068</td> </tr> <tr> <td>DSV</td> <td>DKK</td> <td>105.4</td> <td>0.2</td> <td>0.19</td> <td>105.2</td> <td>105.4</td> <td>674,873</td> <td>71,575,035</td> </tr> <tr> <td>FLSmidth &amp; Co.</td> <td>DKK</td> <td>295.8</td> <td>-1.8</td> <td>-0.6</td> <td>295.1</td> <td>295.8</td> <td>341,263</td> <td>100,301,032</td> </tr> <tr> <td>G4S plc</td> <td>DKK</td> <td>22.53</td> <td>0.05</td> <td>0.22</td> <td>22.53</td> <td>22.57</td> <td>190,920</td> <td>4,338,150</td> </tr> <tr> <td>Jyske Bank</td> <td>DKK</td> <td>144.2</td> <td>1.4</td> <td>0.98</td> <td>142.8</td> <td>144.2</td> <td>78,163</td> <td>11,104,874</td> </tr> <tr> <td>Københavns ...</td> <td>DKK</td> <td>1,580.00</td> <td>-12</td> <td>-0.75</td> <td>1,590.00</td> <td>1,620.00</td> <td>82</td> <td>131,110</td> </tr> <tr> <td>Lundbeck</td> <td>DKK</td> <td>103.4</td> <td>-2.5</td> <td>-2.36</td> <td>103.4</td> <td>103.8</td> <td>157,162</td> <td>16,462,282</td> </tr> <tr> <td>Nordea Bank</td> <td>DKK</td> <td>43.22</td> <td>-0.06</td> <td>-0.14</td> <td>43.22</td> <td>43.25</td> <td>167,520</td> <td>7,310,143</td> </tr> <tr> <td>Novo Nordisk B</td> <td>DKK</td> <td>552.5</td> <td>-3.5</td> <td>-0.63</td> <td>550.5</td> <td>552.5</td> <td>843,533</td> <td>463,962,375</td> </tr> <tr> <td>Novozymes B</td> <td>DKK</td> <td>805.5</td> <td>5.5</td> <td>0.69</td> <td>805</td> <td>805.5</td> <td>152,188</td> <td>121,746,199</td> </tr> <tr> <td>Pandora</td> <td>DKK</td> <td>39.04</td> <td>0.94</td> <td>2.47</td> <td>38.8</td> <td>39.04</td> <td>350,965</td> <td>13,611,838</td> </tr> <tr> <td>Rockwool In...</td> <td>DKK</td> <td>492</td> <td>0</td> <td>0</td> <td>482</td> <td>492</td> <td></td> <td></td> </tr> <tr> <td>Rockwool In...</td> <td>DKK</td> <td>468</td> <td>12</td> <td>2.63</td> <td>465.2</td> <td>468</td> <td>9,885</td> <td>4,623,850</td> </tr> <tr> <td>Sydbank</td> <td>DKK</td> <td>95</td> <td>0.05</td> <td>0.05</td> <td>94.7</td> <td>95</td> <td>103,438</td> <td>9,802,899</td> </tr> <tr> <td>TDC</td> <td>DKK</td> <td>43.6</td> <td>0.13</td> <td>0.3</td> <td>43.5</td> <td>43.6</td> <td>845,110</td> <td>36,785,339</td> </tr> <tr> <td>Topdanmark</td> <td>DKK</td> <td>854</td> <td>13.5</td> <td>1.61</td> <td>854</td> <td>855</td> <td>38,679</td> <td>32,737,678</td> </tr> <tr> <td>Tryg</td> <td>DKK</td> <td>290.4</td> <td>0.3</td> <td>0.1</td> <td>290</td> <td>290.4</td> <td>94,587</td> <td>27,537,247</td> </tr> <tr> <td>Vestas Wind...</td> <td>DKK</td> <td>90.15</td> <td>-4.2</td> <td>-4.45</td> <td>90.1</td> <td>90.15</td> <td>1,317,313</td> <td>121,064,314</td> </tr> <tr> <td>William Dem...</td> <td>DKK</td> <td>417.6</td> <td>0.1</td> <td>0.02</td> <td>417</td> <td>417.6</td> <td>64,242</td> <td>26,859,554</td> </tr> </tbody> </table> <div style="height: 4000px">lots of content down here...</div> 

Here is an improved answer to the one posted by Maximilian Hils .

This one works in IE11 with no flickering whatsoever:

 var headerCells = tableWrap.querySelectorAll("thead td"); for (var i = 0; i < headerCells.length; i++) { var headerCell = headerCells[i]; headerCell.style.backgroundColor = "silver"; } var lastSTop = tableWrap.scrollTop; tableWrap.addEventListener("scroll", function () { var stop = this.scrollTop; if (stop < lastSTop) { // reseting the transform for the scrolling up to hide the headers for (var i = 0; i < headerCells.length; i++) { headerCells[i].style.transitionDelay = "0s"; headerCells[i].style.transform = ""; } } lastSTop = stop; var translate = "translate(0," + stop + "px)"; for (var i = 0; i < headerCells.length; i++) { headerCells[i].style.transitionDelay = "0.25s"; headerCells[i].style.transform = translate; } }); 

I like Maximillian Hils' answer but I had a some issues:

  1. the transform doesn't work in Edge or IE unless you apply it to the th
  2. the header flickers during scrolling in Edge and IE
  3. my table is loaded using ajax, so I wanted to attach to the window scroll event rather than the wrapper's scroll event

To get rid of the flicker, I use a timeout to wait until the user has finished scrolling, then I apply the transform – so the header is not visible during scrolling.

I have also written this using jQuery, one advantage of that being that jQuery should handle vendor-prefixes for you

  var isScrolling; //Scroll events don't bubble https://stackoverflow.com/a/19375645/150342 //so can't use $(document).on("scroll", ".table-container-fixed", function (e) { document.addEventListener('scroll', function (event) { var $container = $(event.target); if (!$container.hasClass("table-container-fixed")) return; //transform needs to be applied to th for Edge and IE //in this example I am also fixing the leftmost column var $topLeftCell = $container.find('table > thead > tr > th:first'); var $headerCells = $topLeftCell.siblings(); var $columnCells = $container.find('table tr > td:first-child'); //hide the cells otherwise they are visible while scrolling back again $topLeftCell.css('visibility', 'hidden'); $headerCells.css('visibility', 'hidden'); $columnCells.css('visibility', 'hidden'); // Using timeout to delay transform until user stops scrolling // Clear timeout while scrolling window.clearTimeout(isScrolling); // Set a timeout to run after scrolling ends isScrolling = setTimeout(function () { //move the table cells. var x = $container.scrollLeft(); var y = $container.scrollTop(); $topLeftCell.css('transform', 'translate(' + x + 'px, ' + y + 'px)'); $headerCells.css('transform', 'translateY(' + y + 'px)'); $columnCells.css('transform', 'translateX(' + x + 'px)'); $topLeftCell.css('visibility', 'visible'); $headerCells.css('visibility', 'visible'); $columnCells.css('visibility', 'visible'); }, 100); }, true); 

The table is wrapped in a div with the class table-container-fixed .

 .table-container-fixed{ overflow: auto; height: 400px; } 

I set border-collapse to separate because otherwise we lose borders during translation, and I remove the border on the table to stop content appearing just above the cell where the border was during scrolling.

 .table-container-fixed > table { border-collapse: separate; border:none; } 

I make the th background white to cover the cells underneath, and I add a border that matches the table border – which is styled using Bootstrap and scrolled out of view. 2px because I lose a pixel in the javascript:

  .table-container-fixed > table > thead > tr > th { border-top: 2px solid #ddd !important; background-color: white; } .table-container-fixed > table > thead > tr > th:first-child { z-index: 300; position: relative;/*to make z-index work*/ } .table-container-fixed > table > tbody > tr > td:first-child, .table-container-fixed > table > tfoot > tr > td:first-child { background-color: white; }