Make TBODY scrollable in Webkit browsers - html

I'm aware of this question, but none of the answers work in Safari, Chrome, etc.
The accepted strategy (as demonstrated here) is to set the tbody height and overflow properties like so:
<table>
<thead>
<tr><th>This is the header and doesn't scroll</th></tr>
</thead>
<tbody style="height:100px; overflow:auto;">
<tr><td>content that scrolls</td></tr>
<tr><td>content that scrolls</td></tr>
<tr><td>content that scrolls</td></tr>
<tr><td>content that scrolls</td></tr>
<tr><td>content that scrolls</td></tr>
<tr><td>content that scrolls</td></tr>
<tr><td>content that scrolls</td></tr>
</tbody>
</table>
Unfortunately, this does not work in any webkit browsers. There is a bug report about it that doesn't seem to be a high priority (reported June 05).
So my question is: are there alternate strategies that do actually work? I've tried the two-table approach, but it's impossible to guarantee that the header will line up with the content. Do I just have to wait for Webkit to fix it?

Here is a working example:
http://www.imaputz.com/cssStuff/bigFourVersion.html
You have to add the display:block to the thead > tr and tbody

Using the display:block style only works if you have 1 column. If you have multiple data columns - with multiple fields - then display:block appears to make all data columns scrollable but under the 1st column (does the same in Firefox - which is the only browser I know that does tbody scrolling nicely). Incidentally, on Firefox - you can use the overflow-x: hidden style to suppress the horizontal scroll.
I realized that the issue I mention only occurs if you are not specifying a width for the th & td elements - if you can fix the column widths then it works. Problem for me is I can't fix the column widths.

Try the first method of this page, pure CSS with a single table (2 divs around the table, and the thead is positionned absolute) : http://www.cssplay.co.uk/menu/tablescroll.html
Seems to work on FF4/IE9/IE8 in addition to IE7/FF3.6.

I had the same issue and wrote a jQuery script to do this for me... uses two table elements and formats the css accordingly. Hope this helps others who have the same issue...
http://jsfiddle.net/pe295/1/

I saw Sean Haddy's excellent solution and took the liberty of making some edits:
Use classes instead of ID, so one jQuery script could be reused for
multiple tables on one page
Added support for semantic HTML table elements like caption, thead, tfoot, and tbody
Made scrollbar optional so it won't appear for tables that are "shorter" than the scrollable height
Adjusted scrolling div's width to bring the scrollbar up to the right edge of the table
Made concept accessible by
using aria-hidden="true" on injected static table header
and leaving original thead in place, just hidden with jQuery and set aria-hidden="false"
Showed examples of multiple tables with different sizes
Sean did the heavy lifting, though. Thanks to Matt Burland, too, for pointing out need to support tfoot.
Please see for yourself at http://jsfiddle.net/jhfrench/eNP2N/

A faced the same problem long ago, and I finally set out the two tables approach. This is the result: http://jsfiddle.net/bGr4V/3/, it works for all browsers (IE6+ incl).
In this jsFiddle you can play with a clean version.
My solution was to add a fix cell <th class="fix"> </th> in thead to fill the space of the scroll bar in the tbody, then give one column a variable width (<td class="grow">), so the header fix cell wouldn't unmatch on resizing.
HTML:
<div class="fixed_table">
<div class="head">
<table>
<thead>
<tr>
<th>Column header</th>
<th class="grow">Column header</th>
<th class="fix"> </th>
</tr>
</thead>
</table>
<div class="body">
<table class="full_table">
<caption class="caption">Caption</caption>
<tbody>
<tr>
<td>Data</td>
<td class="grow">Data</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
</tr>
</tbody>
</table>
</div>
</div>
CSS: has * and _ hack for ie6-7, and a -webkit specific for the header fix-cell matching scroll width in each case.
.fixed_table table {
table-layout: fixed;
width: auto;
border-width: 0;
padding: 0;
border-collapse: collapse;
}
.fixed_table .body {
overflow: scroll;
overflow-x: hidden;
max-height: 10.75em;
min-height: 2.5em;
padding-bottom: 0.8em;
*padding-right: 17px; /*ie7 & ie6*/
_padding-right: 0; /*ie6*/
_height: 10em ;
}
.fixed_table th, .fixed_table td {
width: 4.7em;
}
.fixed_table .grow {
width: auto;
}
.fixed_table .fix {
width: 16px;
*width: 17px; /*ie7 & ie6*/
_width: 16px; /*ie6*/
}
/* webkit specific */
#media screen and (-webkit-min-device-pixel-ratio:0) {
.fixed_table .fix{ width: 17px }
}

Thought I'd throw my solution into the mix - http://tjvantoll.com/2012/11/10/creating-cross-browser-scrollable-tbody/.
It takes the same basic route as #Michael Koper's solution but includes a workaround so the table will look correct in IE back to IE6.
I solve the width issue by giving the <table> a table-layout: fixed and explicitly assigning width to cells in each column. Not ideal but it does produce a semantic table that will align cross browser regardless of whether a scrollbar is needed.
Demo: http://codepen.io/tjvantoll/pen/JEKIu

I developed javascript solution for the above problem
which works only in Firefox 7+ as i have tested only in FF
I came to this thread and found solution pointed by Michael Koper
In this solution three important things are done
1) fix the column width
2) thead > tr display is set to block
3) tbody display is set to block
as others have mentioned there problem to fix the width , i am also in same position;
even i cant fix the width statically
so i thought i will fix the width dynamically ( after table is rendered in browser)
and this did the trick :)
following is the solution in javascript which works only in FF
( i have tested only in FF , i dont have access to other browsers )
function test1(){
var tbodys = document.getElementsByTagName("TBODY");
for(var i=0;i<tbodys.length;i++){
do_tbodyscroll(tbodys[i]);
}
}
function do_tbodyscroll(_tbody){
// get the table node
var table = _tbody.parentNode;
// first row of tbody
var _fr = _tbody.getElementsByTagName("TR")[0];
// first row cells ..
var _frcells = _fr.cells;
// Width array , width of each element is stored in this array
var widtharray = new Array(_frcells.length);
for(var i=0;i<_frcells.length;i++){
widtharray[i] = _frcells[i].scrollWidth;
}
// Apply width to first row
for(var i=0;i<_frcells.length;i++){
_frcells[i].width = widtharray[i];
}
// Get the Last row in Thead ...
// COLGROUPS USING COLSPAN NOT YET SUPPORTED
var thead = table.getElementsByTagName("THEAD")[0];
var _rows = thead.getElementsByTagName("TR");
var tableheader = _rows[_rows.length - 1];
var headercells = tableheader.cells;
// Apply width to header ..
for(var i=0;i<headercells.length;i++){
headercells[i].width = widtharray[i];
}
// ADD 16 Pixel of scroll bar to last column ..
headercells[headercells.length -1 ].width = widtharray[headercells.length -1] + 16;
tableheader.style.display = "block";
_tbody.style.display = "block";
}
This solutions finds out what is the width of column from browser
and set again the same width to columns ( header and first row of tbody )
after the width is set; thead > tr and tbody display is set to block
Hope this solution is useful for all of you ..
if you can extend it to other browsers please reply to this post

This is really quite hacky but maybe it will help someone somewhere...
http://jsfiddle.net/yUHCq/1/
It uses columns instead of rows so processing it would essentially be done backwards.
Transitional DOCTYPE added for IE compatibility.

It may be overkill for this question, but YUI still provides possibly the best free cross-browser datatable. It can be used for read-only data as well.
YUI 2 DataTable
YUI 3 DataTable
Click on the examples there to see scrollable samples. Since, I am a newbie to stackoverflow, I can only post these two links.

Add display:block; This will also remove the unnecessary horizontal scroll in FireFox as well. You are also, no doubt, aware that neither example works in MSIE 8.
<table>
<thead>
<tr><th>This is the header and doesn't scroll</th></tr>
</thead>
<tbody style="height:100px; overflow:auto;display:block;">
<tr><td>content that scrolls</td></tr>
<tr><td>content that scrolls</td></tr>
<tr><td>content that scrolls</td></tr>
<tr><td>content that scrolls</td></tr>
<tr><td>content that scrolls</td></tr>
<tr><td>content that scrolls</td></tr>
<tr><td>content that scrolls</td></tr>
</tbody>
</table>

There is an example here that works in IE5+, Firefox and Chrome. However, it uses fixed width columns.
http://www.cssplay.co.uk/menu/tablescroll.html

If anyone needs it to work in IE quirks mode, here is how I changed and simplified I.G. Pascual's code to work:
.fixed_table{
overflow: scroll;
overflow-x: hidden;
max-height: 300px;
height: 300px;
min-height: 50px;
padding-right:0;
}
#datatable td
{
padding:3px;
text-align: center;
width: 9%;
vertical-align: middle;
background-color: #343435;
color: #E0E0E3;
overflow:hidden;
}
<div class="head">
<table>
<thead>
<tr>
<th>Time (GMT)</th>
<th>Price</th>
</tr>
</thead>
</table>
</div>
<div class="fixed_table">
<table id="datatable" cellspacing="1" cellpadding="1">
<tbody></tbody>
</table>
</div>

Let the table draw as it's way and calculate each column's width and set it in to each heading. Headings are made with divisions and then we can let the table to be scrolled free.
<!DOCTYPE html>
<html>
<head>
<style>
.t_heading{
margin-top: 20px;
font-family: Verdana, Geneva, sans-serif;
background-color: #4CAF50;
}
.heading{
background-color: #4CAF50;
color: white;
float:left;
padding: 8px 0PX 8PX 0PX;
border-bottom: 1px solid #ddd;
height: 20px;
text-align: left;
}
.t_data{
overflow-y: scroll;
overflow-x: hidden;
height:0px;
width: 100%;
}
.t_content{
border-collapse: collapse;
font-family: Verdana, Geneva, sans-serif;
width: 100%;
}
.column1,.column2,.column3,.column4{
padding: 8px 0PX 8PX 0PX;
text-align: left;
border-bottom: 1px solid #ddd;
}
.t_row:hover{
background-color:#f5f5f5;
}
</style>
<script>
function setBody(){
var body = document.body,
html = document.documentElement;
var height = Math.max( body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight );
height = height-300;
/*
** By changing the subtraction value, You can fit the table in to the screen correctly.
** Make sure not to come the hscroll.
** Or you can set fixed height with css for the div as your wish.
*/
document.getElementById("t_data").style.height = height+"px";
setColumnWidth("column1");
setColumnWidth("column2");
setColumnWidth("column3");
setColumnWidth("column4");
}
function setColumnWidth(o){
var object = o;
var x = document.getElementsByClassName(object);
var y = x[0].clientWidth;
document.getElementById(object).style.width = y+"px";
}
</script>
</head>
<body onload="setBody()">
<div class="t_heading">
<div class="heading" id="column1">Heading 1</div>
<div class="heading" id="column2">Heading 2</div>
<div class="heading" id="column3">Heading 3</div>
<div class="heading" id="column4">Heading 4</div>
</div>
<div class="t_data" id="t_data">
<table class="t_content">
<tr class='t_row'>
<td class='column1'>Column1 Row 0</td>
<td class='column2'>Column2 Row 0</td>
<td class='column3'>Column3 Row 0</td>
<td class='column4'>Column4 Row 0</td>
</tr><tr class='t_row'>
<td class='column1'>Column1 Row 1</td>
<td class='column2'>Column2 Row 1</td>
<td class='column3'>Column3 Row 1</td>
<td class='column4'>Column4 Row 1</td>
</tr><tr class='t_row'>
<td class='column1'>Column1 Row 2</td>
<td class='column2'>Column2 Row 2</td>
<td class='column3'>Column3 Row 2</td>
<td class='column4'>Column4 Row 2</td>
</tr><tr class='t_row'>
<td class='column1'>Column1 Row 3</td>
<td class='column2'>Column2 Row 3</td>
<td class='column3'>Column3 Row 3</td>
<td class='column4'>Column4 Row 3</td>
</tr><tr class='t_row'>
<td class='column1'>Column1 Row 4</td>
<td class='column2'>Column2 Row 4</td>
<td class='column3'>Column3 Row 4</td>
<td class='column4'>Column4 Row 4</td>
</tr><tr class='t_row'>
<td class='column1'>Column1 Row 5</td>
<td class='column2'>Column2 Row 5</td>
<td class='column3'>Column3 Row 5</td>
<td class='column4'>Column4 Row 5</td>
</tr><tr class='t_row'>
<td class='column1'>Column1 Row 6</td>
<td class='column2'>Column2 Row 6</td>
<td class='column3'>Column3 Row 6</td>
<td class='column4'>Column4 Row 6</td>
</tr><tr class='t_row'>
<td class='column1'>Column1 Row 7</td>
<td class='column2'>Column2 Row 7</td>
<td class='column3'>Column3 Row 7</td>
<td class='column4'>Column4 Row 7</td>
</tr><tr class='t_row'>
<td class='column1'>Column1 Row 8</td>
<td class='column2'>Column2 Row 8</td>
<td class='column3'>Column3 Row 8</td>
<td class='column4'>Column4 Row 8</td>
</tr><tr class='t_row'>
<td class='column1'>Column1 Row 9</td>
<td class='column2'>Column2 Row 9</td>
<td class='column3'>Column3 Row 9</td>
<td class='column4'>Column4 Row 9</td>
</tr><tr class='t_row'>
<td class='column1'>Column1 Row 10</td>
<td class='column2'>Column2 Row 10</td>
<td class='column3'>Column3 Row 10</td>
<td class='column4'>Column4 Row 10</td>
</tr>
<!--
<?php
$data = array();
for($a = 0; $a<50; $a++)
{
$data[$a] = array();
$data[$a][0] = "Column1 Row ".$a;
$data[$a][1] = "Column2 Row ".$a;
$data[$a][2] = "Column3 Row ".$a;
$data[$a][3] = "Column4 Row ".$a;
}
/*
** supose you have data in an array.. which red from database. The table will draw using array data. Or you can draw the table manualy.
** tip: If using manual table, No need of
** 'var x = document.getElementsByClassName(object); var y = x[0].clientWidth;'.
** You can just set ID of first row's cells in to some name and use it to read width.
*/
for($i=0;$i<sizeof($data);$i++){
echo "<tr class='t_row'><td class='column1'>".$data[$i][0]."</td><td class='column2'>".$data[$i][1]."</td><td class='column3'>".$data[$i][2]."</td><td class='column4'>".$data[$i][3]."</td></tr>";
}
?>
-->
</table>
</div>
</body>
</html>

Related

How do i make a table in html with first row having 1 column only and other rows having more than 1

I have tried to make this work but it always adds an extra column in the first row even when i clearly stated it has only 1 column. What i want to make is like this : this is what i want to make
But this is what i get like this
The only way to make it like the first picture is by using 2 tables which is what i used but is there no way to do it with 1 table ?
My code :my code for the second picture
Use the colspan attribute.
td {
border: solid black 1px;
height: 20px;
}
<table>
<tr>
<td colspan="3">colspan = 3</td>
</tr>
<tr>
<td>colspan = 1</td>
<td>colspan = 1</td>
<td>colspan = 1</td>
</tr>
<tr>
<td colspan="3">colspan = 3</td>
</tr>
</table>
You can use the colspan attribute.
The colspan attribute in HTML specifies the number of columns a cell should span. It allows the single table cell to span the width of more than one cell or column.
Below is a working code snippet which looks almost similar to your requirement.
table, tr, td, th {
border: 1px solid black;
border-collapse: collapse;
}
table {
width: 100%;
}
.row1, .row2 {
height: 100px;
}
.row2 {
vertical-align: top;
}
.row1, .row3 {
text-align: center;
}
<table>
<tr class="row1">
<td colspan="3">Your Name</td>
</tr>
<tr class="row2">
<td>Course 1</td>
<td>Course 2</td>
<td>Course 3</td>
</tr>
<tr colspan="3" class="row3">
<td colspan="3">Social Media accounts</td>
</tr>
</table>

html table colspan not working as expected

HTML concepts are so terrible sometimes.
This is my code using colspan in html table, and doesn't look as I expect.
<table border="1">
<tr>
<td colspan="3">a</td>
</tr>
<tr>
<td colspan="2">b</td>
<td>c</td>
</tr>
</table>
What I want is: cell 'a' should look 3 cell wide, cell 'b' should look 2 cell wide, cell 'c' should look 1 cell wide.
What it is doing is: cell 'a' is 2 cells wide, cell 'b' & 'c' is 1 cell wide.
Any Suggestions thanks.
Attribute colspan determines how many columns a cell overlaps with respect to other cells, not the absolute size of those columns. In your case, span 2 has two spans. how you can say it is not? don't judge it by width of a cell. span is not the width. You have to add another smaller columns to appear it as a column of two spans.
See the solution for your expectation in code snippet last example.
<h3>Example 1</h3>
<table border="1">
<tr><td>col1</td><td>col2</td><td>col3</td></tr>
<tr><td colspan="3">a</td></tr>
<tr><td colspan="2">b</td><td colspan="1">c</td></tr>
</table>
<h3>Example 2</h3>
<table border="1">
<tr><td width="80px">wide col1</td><td>col2</td><td>col3</td></tr>
<tr><td colspan="3">span 3</td></tr>
<tr><td colspan="2">span 2</td><td colspan="1">span 1</td></tr>
</table>
<h3>Your case</h3>
<table border="1">
<tr><td colspan="3">span 3</td></tr>
<tr><td colspan="2" width="66%">span 2</td><td width="33%">span 1</td></tr>
</table>
You have already answered your question as your code is working fine but your max count is only 2. If you add another row you can see the desired results.
table {
width: 300px;
]
<table border="1">
<tr>
<td colspan="3">column 1</td>
</tr>
<tr>
<td colspan="2">column 2</td>
<td>column 3</td>
</tr>
<tr>
<td>column n</td>
<td>column n</td>
<td>column n</td>
</tr>
</table>
table {
width: 200px;
}
<table border="1">
<tr>
<td>column</td>
<td>column</td>
<td>column</td>
</tr>
<tr>
<td colspan="3">column 1</td>
</tr>
<tr>
<td colspan="2">column 2</td>
<td>column 3</td>
</tr>
</table>
Your code will show the desired result just as you will add another row!!!
So colspans do exactly what they're intended for. There are n columns in the table and each table cell <td> is one column wide. Using colspan spans the cell over more columns. This has nothing to do to each column's width.
If you wish for all columns to be of the same width, you have to use some styling.
Even though it might be easier to use some CSS grid, a solution for table is as follows:
/* You can make a separate file where you calculate n dynamically and you just
* link the file with :root { ... } into html <head> */
:root {
--n: 3; /*number of columns */
}
div {
/* container for mobile because max-width only works on block elements */
width: 30em;
max-width: 100%;
}
table {
/* table takes the whole container */
width: 100%;
}
/* the following is enough for this example */
/* td:not([colspan]) {
width: calc(100% / var(--n));
} */
/* if you don't have any (or well enough placed) table cells without colspan
* attribute, then you have to calculate each possibilty like so: */
td {
width: calc(100% / var(--n));
}
td[colspan="2"] {
width: calc(100% * 2 / var(--n));
}
td[colspan="3"] {
width: calc(100% * 3 / var(--n));
}
/* and so forth up to maximum possible n */
<div>
<table border=1>
<tr>
<td colspan=2>b</td>
<td>a</td>
</tr>
<tr>
<td colspan=3>c</td>
</tr>
</table>
</div>

Scroll inside tbody [duplicate]

Is there a cross-browser CSS/JavaScript technique to display a long HTML table such that the column headers stay fixed on-screen and do not scroll with the table body. Think of the "freeze panes" effect in Microsoft Excel.
I want to be able to scroll through the contents of the table, but to always be able to see the column headers at the top.
This can be cleanly solved in four lines of code.
If you only care about modern browsers, a fixed header can be achieved much easier by using CSS transforms. Sounds odd, but works great:
HTML and CSS stay as-is.
No external JavaScript dependencies.
Four lines of code.
Works for all configurations (table-layout: fixed, etc.).
document.getElementById("wrap").addEventListener("scroll", function(){
var translate = "translate(0,"+this.scrollTop+"px)";
this.querySelector("thead").style.transform = translate;
});
Support for CSS transforms is widely available except for Internet Explorer 8-.
Here is the full example for reference:
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>
I was looking for a solution for this for a while and found most of the answers are not working or not suitable for my situation, so I wrote a simple solution with jQuery.
This is the solution outline:
Clone the table that needs to have a fixed header, and place the
cloned copy on top of the original.
Remove the table body from top table.
Remove the table header from bottom table.
Adjust the column widths. (We keep track of the original column widths)
Below is the code in a runnable demo.
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 src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<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>
This solution works in Chrome and IE. Since it is based on jQuery, this should work in other jQuery supported browsers as well.
I've just completed putting together a jQuery plugin that will take valid single table using valid HTML (have to have a thead and tbody) and will output a table that has fixed headers, optional fixed footer that can either be a cloned header or any content you chose (pagination, etc.). If you want to take advantage of larger monitors it will also resize the table when the browser is resized. Another added feature is being able to side scroll if the table columns can not all fit in view.
http://fixedheadertable.com/
on github: http://markmalek.github.com/Fixed-Header-Table/
It's extremely easy to setup and you can create your own custom styles for it. It also uses rounded corners in all browsers. Keep in mind I just released it, so it's still technically beta and there are very few minor issues I'm ironing out.
It works in Internet Explorer 7, Internet Explorer 8, Safari, Firefox and Chrome.
I also created a plugin that addresses this issue. My project - jQuery.floatThead has been around for over 4 years now and is very mature.
It requires no external styles and does not expect your table to be styled in any particular way. It supports Internet Explorer9+ and Firefox/Chrome.
Currently (2018-05) it has:
405 commits and 998 stars on GitHub
Many (not all) of the answers here are quick hacks that may have solved the problem one person was having, but will work not for every table.
Some of the other plugins are old and probably work great with Internet Explorer, but will break on Firefox and Chrome.
All of the attempts to solve this from outside the CSS specification are pale shadows of what we really want: Delivery on the implied promise of THEAD.
This frozen-headers-for-a-table issue has been an open wound in HTML/CSS for a long time.
In a perfect world, there would be a pure-CSS solution for this problem. Unfortunately, there doesn't seem to be a good one in place.
Relevant standards-discussions on this topic include:
The Sticky Positioning proposal at www-style: http://lists.w3.org/Archives/Public/www-style/2012Jun/0627.html
Tab Atkins' proposal for position-root, position-contain or position-restrict: http://www.xanthir.com/blog/b48H0
UPDATE: Firefox shipped position:sticky in version 32. Everyone wins!
TL;DR
If you target modern browsers and don't have extravagant styling needs: http://jsfiddle.net/dPixie/byB9d/3/ ... Although the big four version is pretty sweet as well this version handles fluid width a lot better.
Good news everyone!
With the advances of HTML5 and CSS3 this is now possible, at least for modern browsers. The slightly hackish implementation I came up with can be found here: http://jsfiddle.net/dPixie/byB9d/3/. I have tested it in FX 25, Chrome 31 and IE 10 ...
Relevant HTML (insert a HTML5 doctype at the top of your document though):
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;
}
<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>
But how?!
Simply put you have a table header, that you visually hide by making it 0px high, that also contains divs used as the fixed header. The table's container leaves enough room at the top to allow for the absolutely positioned header, and the table with scrollbars appear as you would expect.
The code above uses the positioned class to position the table absolutely (I'm using it in a popup style dialog) but you can use it in the flow of the document as well by removing the positioned class from the container.
But ...
It's not perfect. Firefox refuses to make the header row 0px (at least I did not find any way) but stubbornly keeps it at minimum 4px ... It's not a huge problem, but depending on your styling it will mess with your borders etc.
The table is also using a faux column approach where the background color of the container itself is used as the background for the header divs, that are transparent.
Summary
All in all there might be styling issues depending on your requirements, especially borders or complicated backgrounds. There might also be problems with computability, I haven't checked it in a wide variety of browsers yet (please comment with your experiences if you try it out), but I didn't find anything like it so I thought it was worth posting anyway ...
The CSS property position: sticky has great support in most modern browsers (I had issues with Edge, see below).
This lets us solve the problem of fixed headers quite easily:
thead th { position: sticky; top: 0; }
Safari needs a vendor prefix: -webkit-sticky.
For Firefox, I had to add min-height: 0 to one the parent elements. I forget exactly why this was needed.
Most unfortunately, the Microsoft Edge implementation seems to be only semi-working. At least, I had some flickering and misaligned table cells in my testing. The table was still usable, but had significant aesthetic issues.
Here is a jQuery plugin for fixed table headers. It allows the entire page to scroll, freezing the header when it reaches the top. It works well with Twitter Bootstrap tables.
GitHub repository: https://github.com/oma/table-fixed-header
It does not scroll only table content. Look to other tools for that, as one of these other answers. You decide what fits your case the best.
Most of the solutions posted here require jQuery. If you are looking for a framework independent solution try Grid: http://www.matts411.com/post/grid/
It's hosted on Github here: https://github.com/mmurph211/Grid
Not only does it support fixed headers, it also supports fixed left columns and footers, among other things.
A more refined pure CSS scrolling table
All of the pure CSS solutions I've seen so far-- clever though they may be-- lack a certain level of polish, or just don't work right in some situations. So, I decided to create my own...
Features:
It's pure CSS, so no jQuery required (or any JavaScript code at all, for that
matter)
You can set the table width to a percent (a.k.a. "fluid") or a fixed value, or let the content determine its width (a.k.a. "auto")
Column widths can also be fluid, fixed, or auto.
Columns will never become misaligned with headers due to horizontal scrolling (a problem that occurs in every other CSS-based solution I've seen that doesn't require fixed widths).
Compatible with all of the popular desktop browsers, including Internet Explorer back to version 8
Clean, polished appearance; no sloppy-looking 1-pixel gaps or misaligned borders; looks the same in all browsers
Here are a couple of fiddles that show the fluid and auto width options:
Fluid Width and Height (adapts to screen size): jsFiddle (Note that the scrollbar only shows up when needed in this configuration, so you may have to shrink the frame to see it)
Auto Width, Fixed Height (easier to integrate with other content): jsFiddle
The Auto Width, Fixed Height configuration probably has more use cases, so I'll post the code below.
/* 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;
color: black;
}
.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 */
<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 two 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>
<!--[if lte IE 9]><style>.scrollingtable > div > div > table {margin-right: 17px;}</style><![endif]-->
The method I used to freeze the header row is similar to d-Pixie's, so refer to his post for an explanation. There were a slew of bugs and limitations with that technique that could only be fixed with heaps of additional CSS and an extra div container or two.
A simple jQuery plugin
This is a variation on Mahes' solution. You can call it like $('table#foo').scrollableTable();
The idea is:
Split the thead and tbody into separate table elements
Make their cell widths match again
Wrap the second table in a div.scrollable
Use CSS to make div.scrollable actually scroll
The CSS could be:
div.scrollable { height: 300px; overflow-y: scroll;}
Caveats
Obviously, splitting up these tables makes the markup less semantic. I'm not sure what effect this has on accessibility.
This plugin does not deal with footers, multiple headers, etc.
I've only tested it in Chrome version 20.
That said, it works for my purposes and you're free to take and modify it.
Here's the plugin:
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);
};
:)
Not-so-clean, but pure HTML/CSS solution.
table {
overflow-x:scroll;
}
tbody {
max-height: /*your desired max height*/
overflow-y:scroll;
display:block;
}
Updated for IE8+
JSFiddle example
Somehow I ended up with Position:Sticky working fine on my case:
table{
width: 100%;
border: collapse;
}
th{
position: sticky;
top: 0px;
border: 1px solid black;
background: #ff5722;
color: #f5f5f5;
font-weight: 600;
}
td{
background: #d3d3d3;
border: 1px solid black;
color: #f5f5f5;
font-weight: 600;
}
div{
height: 150px
overflow: auto;
width: 100%
}
<div>
<table>
<thead>
<tr>
<th>header 1</th>
<th>header 2</th>
<th>header 3</th>
<th>header 4</th>
<th>header 5</th>
<th>header 6</th>
<th>header 7</th>
</tr>
</thead>
<tbody>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
<tr>
<td>data 1</td>
<td>data 2</td>
<td>data 3</td>
<td>data 4</td>
<td>data 5</td>
<td>data 6</td>
<td>data 7</td>
</tr>
</tbody>
</table>
</div>
Support for fixed footer
I extended Nathan's function to also support a fixed footer and maximum height.
Also, the function will set the CSS itself, and you only have to support a width.
Usage:
Fixed height:
$('table').scrollableTable({ height: 100 });
Maximum height (if the browser supports the CSS 'max-height' option):
$('table').scrollableTable({ maxHeight: 100 });
Script:
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();
// Grab the 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();
};
Two divs, one for header, one for data. Make the data div scrollable, and use JavaScript to set the width of the columns in the header to be the same as the widths in the data. I think the data columns widths need to be fixed rather than dynamic.
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>
I realize the question allows JavaScript, but here is a pure CSS solution I worked up that also allows for the table to expand horizontally. It was tested with Internet Explorer 10 and the latest Chrome and Firefox browsers. A link to jsFiddle is at the bottom.
The 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 its 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>
And the 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/
For those who tried the nice solution given by Maximilian Hils, and did not succeed to get it to work with Internet Explorer, I had the same problem (Internet Explorer 11) and found out what was the problem.
In Internet Explorer 11 the style transform (at least with translate) does not work on <THEAD>. I solved this by instead applying the style to all the <TH> in a loop. That worked. My JavaScript code looks like this:
document.getElementById('pnlGridWrap').addEventListener("scroll", function () {
var translate = "translate(0," + this.scrollTop + "px)";
var myElements = this.querySelectorAll("th");
for (var i = 0; i < myElements.length; i++) {
myElements[i].style.transform=translate;
}
});
In my case the table was a GridView in ASP.NET. First I thought it was because it had no <THEAD>, but even when I forced it to have one, it did not work. Then I found out what I wrote above.
It is a very nice and simple solution. On Chrome it is perfect, on Firefox a bit jerky, and on Internet Explorer even more jerky. But all in all a good solution.
Very late to the party, but as it's still a party, here's my two cents using tailwindcss:
<div class="h-screen overflow-hidden flex flex-col">
<div class="overflow-y-scroll flex-1">
<table>
<thead class="sticky top-0">
<tr>
<th>Timestamp</th>
<th>Species</th>
</tr>
</thead>
<tbody>
<tr>
<td>2022-02-09T08:20:39.967Z</td>
<td>willow</td>
</tr>
<tr>
<td>2022-02-09T08:21:29.453Z</td>
<td>red osier dogwood</td>
</tr>
<tr>
<td>2022-02-09T08:22:18.984Z</td>
<td>buttonbush</td>
</tr>
</tbody>
</table>
</div>
</div>
Here's a full working example on tailwind's playgroud.
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();
By applying the StickyTableHeaders jQuery plugin to the table, the column headers will stick to the top of the viewport as you scroll down.
Example:
$(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>A.P. 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>A.P. 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 & 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>
I like Maximillian Hils' answer but I had a some issues:
the transform doesn't work in Edge or IE unless you apply it to the th
the header flickers during scrolling in Edge and IE
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, lastTop, lastLeft, isLeftHidden, isTopHidden;
//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:first > thead > tr > th:first');
var $headerCells = $topLeftCell.siblings();
var $columnCells = $container
.find('table:first > tbody > tr > td:first-child, ' +
'table:first > tfoot > tr > td:first-child');
//hide the cells while returning otherwise they show on top of the data
if (!isLeftHidden) {
var currentLeft = $container.scrollLeft();
if (currentLeft < lastLeft) {
//scrolling left
isLeftHidden = true;
$topLeftCell.css('visibility', 'hidden');
$columnCells.css('visibility', 'hidden');
}
lastLeft = currentLeft;
}
if (!isTopHidden) {
var currentTop = $container.scrollTop();
if (currentTop < lastTop) {
//scrolling up
isTopHidden = true;
$topLeftCell.css('visibility', 'hidden');
$headerCells.css('visibility', 'hidden');
}
lastTop = currentTop;
}
// 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)');
isTopHidden = isLeftHidden = false;
$topLeftCell.css('visibility', 'inherit');
$headerCells.css('visibility', 'inherit');
$columnCells.css('visibility', 'inherit');
}, 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.
.table-container-fixed > table > thead > tr > th {
border-top: 1px solid #ddd !important;
background-color: white;
z-index: 10;
position: relative;/*to make z-index work*/
}
.table-container-fixed > table > thead > tr > th:first-child {
z-index: 20;
}
.table-container-fixed > table > tbody > tr > td:first-child,
.table-container-fixed > table > tfoot > tr > td:first-child {
background-color: white;
z-index: 10;
position: relative;
}
Use the latest version of jQuery, and include the following JavaScript code.
$(window).scroll(function(){
$("id of the div element").offset({top:$(window).scrollTop()});
});
This is not an exact solution to the fixed header row, but I have created a rather ingenious method of repeating the header row throughout the long table, yet still keeping the ability to sort.
This neat little option requires the jQuery tablesorter plugin. Here's how it works:
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>
Obviously, my table has many more rows than this. 193 to be exact, but you can see where the header row repeats. The repeating header row is set up 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);
});
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 Stack Overflow 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 content by accident. There are many things that can go wrong, so just be aware/careful of that. This approach works for me.
Here's a solution that we ended up working with (in order to deal with some edge cases and older versions of Internet Explorer, we eventually also faded out the title bar on scroll then faded 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. It 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();
Here is an improved answer to the one posted by Maximilian Hils.
This one works in Internet Explorer 11 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) {
// Resetting 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 developed a simple light-weight jQuery plug-in for converting a well formatted 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 the number of columns that will be always in view when scrolling horizontally.
Demo & Documentation: http://meetselva.github.io/fixed-table-rows-cols/
GitHub repository: https://github.com/meetselva/fixed-table-rows-cols
Below is the usage for a simple table with a 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'}
]
});
<html>
<head>
<script src="//cdn.jsdelivr.net/npm/jquery#3.2.1/dist/jquery.min.js"></script>
<script>
function stickyTableHead (tableID) {
var $tmain = $(tableID);
var $tScroll = $tmain.children("thead")
.clone()
.wrapAll('<table id="tScroll" />')
.parent()
.addClass($(tableID).attr("class"))
.css("position", "fixed")
.css("top", "0")
.css("display", "none")
.prependTo("#tMain");
var pos = $tmain.offset().top + $tmain.find(">thead").height();
$(document).scroll(function () {
var dataScroll = $tScroll.data("scroll");
dataScroll = dataScroll || false;
if ($(this).scrollTop() >= pos) {
if (!dataScroll) {
$tScroll
.data("scroll", true)
.show()
.find("th").each(function () {
$(this).width($tmain.find(">thead>tr>th").eq($(this).index()).width());
});
}
} else {
if (dataScroll) {
$tScroll
.data("scroll", false)
.hide()
;
}
}
});
}
$(document).ready(function () {
stickyTableHead('#tMain');
});
</script>
</head>
<body>
gfgfdgsfgfdgfds<br/>
gfgfdgsfgfdgfds<br/>
gfgfdgsfgfdgfds<br/>
gfgfdgsfgfdgfds<br/>
gfgfdgsfgfdgfds<br/>
gfgfdgsfgfdgfds<br/>
<table id="tMain" >
<thead>
<tr>
<th>1</th> <th>2</th><th>3</th> <th>4</th><th>5</th> <th>6</th><th>7</th> <th>8</th>
</tr>
</thead>
<tbody>
<tr><td>11111111111111111111111111111111111111111111111111111111</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
<tr><td>1</td><td>2</td><td>3</td><td>4</td><td>5555555</td><td>66666666666</td><td>77777777777</td><td>8888888888888888</td></tr>
</tbody>
</table>
</body>
</html>
Additional to #Daniel Waltrip answer. Table need to enclose with div position: relative in order to work with position:sticky . So I would like to post my sample code here.
CSS
/* Set table width/height as you want.*/
div.freeze-header {
position: relative;
max-height: 150px;
max-width: 400px;
overflow:auto;
}
/* Use position:sticky to freeze header on top*/
div.freeze-header > table > thead > tr > th {
position: sticky;
top: 0;
background-color:yellow;
}
/* below is just table style decoration.*/
div.freeze-header > table {
border-collapse: collapse;
}
div.freeze-header > table td {
border: 1px solid black;
}
HTML
<html>
<body>
<div>
other contents ...
</div>
<div>
other contents ...
</div>
<div>
other contents ...
</div>
<div class="freeze-header">
<table>
<thead>
<tr>
<th> header 1 </th>
<th> header 2 </th>
<th> header 3 </th>
<th> header 4 </th>
<th> header 5 </th>
<th> header 6 </th>
<th> header 7 </th>
<th> header 8 </th>
<th> header 9 </th>
<th> header 10 </th>
<th> header 11 </th>
<th> header 12 </th>
<th> header 13 </th>
<th> header 14 </th>
<th> header 15 </th>
</tr>
</thead>
<tbody>
<tr>
<td> data 1 </td>
<td> data 2 </td>
<td> data 3 </td>
<td> data 4 </td>
<td> data 5 </td>
<td> data 6 </td>
<td> data 7 </td>
<td> data 8 </td>
<td> data 9 </td>
<td> data 10 </td>
<td> data 11 </td>
<td> data 12 </td>
<td> data 13 </td>
<td> data 14 </td>
<td> data 15 </td>
</tr>
<tr>
<td> data 1 </td>
<td> data 2 </td>
<td> data 3 </td>
<td> data 4 </td>
<td> data 5 </td>
<td> data 6 </td>
<td> data 7 </td>
<td> data 8 </td>
<td> data 9 </td>
<td> data 10 </td>
<td> data 11 </td>
<td> data 12 </td>
<td> data 13 </td>
<td> data 14 </td>
<td> data 15 </td>
</tr>
<tr>
<td> data 1 </td>
<td> data 2 </td>
<td> data 3 </td>
<td> data 4 </td>
<td> data 5 </td>
<td> data 6 </td>
<td> data 7 </td>
<td> data 8 </td>
<td> data 9 </td>
<td> data 10 </td>
<td> data 11 </td>
<td> data 12 </td>
<td> data 13 </td>
<td> data 14 </td>
<td> data 15 </td>
</tr>
<tr>
<td> data 1 </td>
<td> data 2 </td>
<td> data 3 </td>
<td> data 4 </td>
<td> data 5 </td>
<td> data 6 </td>
<td> data 7 </td>
<td> data 8 </td>
<td> data 9 </td>
<td> data 10 </td>
<td> data 11 </td>
<td> data 12 </td>
<td> data 13 </td>
<td> data 14 </td>
<td> data 15 </td>
</tr>
<tr>
<td> data 1 </td>
<td> data 2 </td>
<td> data 3 </td>
<td> data 4 </td>
<td> data 5 </td>
<td> data 6 </td>
<td> data 7 </td>
<td> data 8 </td>
<td> data 9 </td>
<td> data 10 </td>
<td> data 11 </td>
<td> data 12 </td>
<td> data 13 </td>
<td> data 14 </td>
<td> data 15 </td>
</tr>
<tr>
<td> data 1 </td>
<td> data 2 </td>
<td> data 3 </td>
<td> data 4 </td>
<td> data 5 </td>
<td> data 6 </td>
<td> data 7 </td>
<td> data 8 </td>
<td> data 9 </td>
<td> data 10 </td>
<td> data 11 </td>
<td> data 12 </td>
<td> data 13 </td>
<td> data 14 </td>
<td> data 15 </td>
</tr>
<tr>
<td> data 1 </td>
<td> data 2 </td>
<td> data 3 </td>
<td> data 4 </td>
<td> data 5 </td>
<td> data 6 </td>
<td> data 7 </td>
<td> data 8 </td>
<td> data 9 </td>
<td> data 10 </td>
<td> data 11 </td>
<td> data 12 </td>
<td> data 13 </td>
<td> data 14 </td>
<td> data 15 </td>
</tr>
<tr>
<td> data 1 </td>
<td> data 2 </td>
<td> data 3 </td>
<td> data 4 </td>
<td> data 5 </td>
<td> data 6 </td>
<td> data 7 </td>
<td> data 8 </td>
<td> data 9 </td>
<td> data 10 </td>
<td> data 11 </td>
<td> data 12 </td>
<td> data 13 </td>
<td> data 14 </td>
<td> data 15 </td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
Demo

IE9 table-layout fixed colspan not respected

I found this topic, mine is related but not the same:
Table rendering with cols and colspan on tds in IE9
The problem I am having is that the 2nd colspan=2 in my table is not being read by IE9, funnily enough it works find in IE7 and IE8, but not IE9. Maybe I've done something completely wrong so here it is:
HTML:
<table id="test">
<tbody>
<tr>
<td>COLSPAN = 1</td>
<td colspan="2">COLSPAN = 2</td>
<td>COLSPAN = 1</td>
<td colspan="2">COLSPAN = 2</td>
</tr>
</tbody>
</table>
CSS:
#test {
width: 100%;
border-spacing: 20px;
border-collapse: separate;
table-layout: fixed;
}
#test td {
position: relative;
background-color: #cccccc;
box-shadow: 3px 3px 2px rgba(0,0,0,0.5);
padding: 10px;
}
jsFiddle: http://jsfiddle.net/DUCPp/1/
What is supposed to happen:
What IE9 gives me:
I am convinced this is a IE9 bug, but I haven't been able to find it on google (maybe I'm not searching the right keywords?). Any solutions or links to bug reports will be greatly appreciated!
UPDATE:
I added an extra column after the 2nd colspan=2 column, and it will render correctly. I have deduced that if the last column in a row has colspan > 1, then it will only be rendered as if colspan = 1.
Any ideas on fixing? I'm now almost positive that this is a IE9 bug <_<
Heh... IE9...
Found a "fix"... idea came from: Colspan on cell in one row seems to prevent setting TD width in all the other rows. Why?
Basically I had to add a empty row with the correct # of empty cells in it:
<table id="test">
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>COLSPAN = 1</td>
<td colspan="2">COLSPAN = 2</td>
<td>COLSPAN = 1</td>
<td colspan="2">COLSPAN = 2</td>
</tr>
</tbody>
</table>
Not pretty... and I needed to remove the padding for the cells in order for it not to display. Sigh...
jsFiddle: http://jsfiddle.net/DUCPp/5/
What you want to do is set the width of columns through TH in the header because that's what the browser will use for determining the width of the table and columns in subsequent rows.
Have a look at the following example:
<table>
<thead>
<tr style="height: 0px;">
<th style="width: 110px; height:0px;"></th>
<th style="width: 160px; height: 0px;"></th>
<th style="width: 210px; height: 0px;"></th>
<th style="width: 110px; height: 0px;"></th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="3">Hello</td>
<td>There</td>
</tr>
</tbody>
</table>
You don't appear to have defined any fixed widths for the columns. You should use something like this before the <tbody>:
<col span="6" style="width:16%;" />
I fixed a similar issue by adding the doctype declaration at the start of my HTML code. See http://www.w3.org/QA/2002/04/valid-dtd-list.html
The specific declaration I added was for version 'HTML 4.01 Transitional'.
i.e. placed before the initial tag.
Hope this helps?

Setting a max height on a table

I am trying to design a page where there are some tables. It seems that styling tables is much more painful than it ought to be.
The problem is the following: The tables should have a fixed height and display either white space at the bottom (when there is too little content) or a vertical scrollbar (when there is too much). Add to this that the tables have a header which should not scroll.
As far as I know, the thead not scrolling is the default behaviour for tables. And a stretching tfoot could serve well for the purpose of filling with white space. Sadly, it seems that every constraint I can put on the table height is cheerfully ignored. I have tried
table {
height: 600px;
overflow: scroll;
}
I have tried with max-height. I have tried to position the table absolutely and give both the top and bottom coordinates. I have tried to manually edit the height in Firebug to see if it was a problem with CSS specificity. I have tried to set the height on the tbody too. Fact is, the table always stays exactly the same height as its content, regardless of my efforts.
Of course I could fake a table with a div structure, but it actually is a table, and I fear using divs I may run into an issue where some columns may not be properly aligned.
How am I supposed to give a table a height?
NOTE this answer is now incorrect. I may get back to it at a later time.
As others have pointed out, you can't set the height of a table unless you set its display to block, but then you get a scrolling header. So what you're looking for is to set the height and display:block on the tbody alone:
<table style="border: 1px solid red">
<thead>
<tr>
<td>Header stays put, no scrolling</td>
</tr>
</thead>
<tbody style="display: block; border: 1px solid green; height: 30px; overflow-y: scroll">
<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>
</tbody>
</table>
Here's the fiddle.
Set display: block; for the table
Set position: sticky; top: 0; for the header row
<table style="display: block; height: 100px; overflow: auto;">
<thead>
<tr>
<td style="position: sticky; top: 0;">Header stays put</td>
<td style="position: sticky; top: 0;">Layout aligned</td>
</tr>
</thead>
<tbody>
<tr>
<td>foo1</td>
<td>Header stays put</td>
</tr>
<tr>
<td>foo2</td>
<td>Header stays put</td>
</tr>
</tbody>
</table>
https://jsfiddle.net/0zxk18fp/
Tested on Chrome, Firefox, Safari, Edge
Add display:block; to the table's css. (in other words.. tell the table to act like a block element rather than a table.)
fiddle here
You can do this by using the following css.
.scroll-thead{
width: 100%;
display: inline-table;
}
.scroll-tbody-y
{
display: block;
overflow-y: scroll;
}
.table-body{
height: /*fix height here*/;
}
Following is the HTML.
<table>
<thead class="scroll-thead">
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody class="scroll-tbody-y table-body">
<tr>
<td>Blah</td>
<td>Blah</td>
</tr>
</tbody>
</table>
JSFiddle
I had a coworker ask how to do this today, and this is what I came up with. I don't love it but it is a way to do it without js and have headers respected. The main drawback however is you lose some semantics due to not having a true table header anymore.
Basically I wrap a table within a table, and use a div as the scroll container by giving it a max-height. Since I wrap the table in a parent table "colspanning" the fake header rows it appears as if the table respects them, but in reality the child table just has the same number of rows.
One small issue due to the scroll bar taking up space the child table column widths wont match up exactly.
Live Demo
Markup
<table class="table-container">
<tbody>
<tr>
<td>header col 1</td>
<td>header col 2</td>
</tr>
<tr>
<td colspan="2">
<div class="scroll-container">
<table>
<tr>
<td>entry1</td>
<td>entry1</td>
</tr>
........ all your entries
</table>
</div>
</td>
</tr>
</tbody>
</table>
CSS
.table-container {
border:1px solid #ccc;
border-radius: 3px;
width:50%;
}
.table-container table {
width: 100%;
}
.scroll-container{
max-height: 150px;
overflow-y: scroll;
}
Seems very similar to this question. From there it seems that this should do the trick:
table {
display: block; /* important */
height: 600px;
overflow-y: scroll;
}
A simple workaround that is available in most of the cases it to wrap the table in a div and then give a max-height to that div:
.scrollable-wrapper {
max-height: 400px;
overflow: auto;
}
/* Add also the following code if sticky header is wanted */
.scrollable-wrapper table thead th {
background: #afa;
position: sticky;
top: 0;
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.4);
}
<div class="scrollable-wrapper">
<table>
<thead>
<tr>
<th>Id</th>
<th>Text</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
Codepen: https://codepen.io/Conejoo/pen/NWpjmYw
In Tables, For minimum table cells height or rows height use css height: in place of min-height:
AND
For Limiting max-height of all cells or rows in table with Javascript:
This script is good for horizontal overflow tables.
This script increase the table width 300px each time (maximum 4000px) until rows shrinks to max-height(160px) , and you can also edit numbers as your need.
var i = 0, row, table = document.getElementsByTagName('table')[0], j = table.offsetWidth;
while (row = table.rows[i++]) {
while (row.offsetHeight > 160 && j < 4000) {
j += 300;
table.style.width = j + 'px';
}
}
Source: HTML Table Solution Max Height Limit For Rows Or Cells By Increasing Table Width, Javascript
Use divs with max height and min height around the content that needs to scroll.
<tr>
<td>
<div>content</div>
</td>
</tr>
td div{
max-height:20px;
}
https://jsfiddle.net/ethanabrace/4w0ksczr/
Just try this.
<div style="max-height: 400px; overflow: scroll">
<!--This is your table-->
<table style="border: 1px solid red">
<thead>
<tr>
<td>Header stays put, no scrolling</td>
</tr>
</thead>
<tbody style="display: block; border: 1px solid green; height: 30px; overflow-y: scroll">
<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>
</tbody>
</table>
</div>
<table style="border: 1px solid red">
<thead>
<tr>
<td>Header stays put, no scrolling</td>
</tr>
</thead>
<tbody id="tbodyMain" style="display: block; border: 1px solid green; height: 30px; overflow-y: scroll">
<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>
</tbody>
</table>
Javascript Section
<script>
$(document).ready(function(){
var maxHeight = Math.max.apply(null, $("body").map(function () { return $(this).height(); }).get());
// alert(maxHeight);
var borderheight =3 ;
// Added some pixed into maxheight
// If you set border then need to add this "borderheight" to maxheight varialbe
$("#tbodyMain").css("min-height", parseInt(maxHeight + borderheight) + "px");
});
</script>
please, refer How to set maximum possible height to your Table Body Fiddle Here