Click here to Skip to main content
15,888,351 members
Articles / Programming Languages / Javascript
Article

Fixed headers in large HTML tables

Rate me:
Please Sign up or sign in to vote.
4.76/5 (34 votes)
24 Jun 2006CPOL2 min read 371.5K   10.7K   53   73
There are quite a lot of ways to fix the header column and rows in HTML tables. But when tables become larger, most of them are not useful because scrolling gets far too slow. In this article, I will show an applicable way for IE.

Introduction

There are quite a lot of ways to fix the header column and rows in HTML tables. But when tables become larger, most of them are not useful because scrolling gets far too slow. In the following sample, I will show an applicable way for IE.

My HTML page contains two divs and a table.

HTML
...
<div id="outerDiv">
 <div id="innerDiv">
  <table>
   ...
  </table>
 </div>
</div>
...

In my sample, the table looks like this (the red border shows the innerDiv):

Sample screenshot

The main idea is to copy the innerDiv with the table three times so that there is a div each for the header row, the header column, the first cell in the header row, and the body of the table.

  • In the first three divs, overflow must be set to hidden.
  • In the body div, overflow can be set to scroll if the body is larger then the available space. Furthermore, the table in the body div needs to be positioned absolutely. Top and Left positions have to be negative (Top = -height of header row, Left = -width of header column) so that the headers are no more visible than the body div.

By copying the whole table, all rows and columns will have equal width and height. If you would only copy the first row of the table to the header div, column width in the header could differ from the body columns. After copying the divs, my outerDiv contains four innerDivs (the red borders show the innerDivs):

Sample screenshot

Finally, the divs have to scroll synchronously. When you scroll to the right, the header row has to move to the right too, and when you scroll to the bottom, the header column needs to move too. I found a nice way to do this here.

Sample screenshot

You can view an online demo here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer software architects
Austria Austria
Hi, my name is Karin Huber. Since 1998 I have been working as a developer and IT consultant focusing on building database oriented web applications. In 2007 my friend Rainer and I decided that we want to build a business based on COTS (component off-the-shelf) software. As a result we founded "software architects".

These days we are offering our first version of the time tracking software called 'time cockpit'. You can find more information at www.timecockpit.com.

Comments and Discussions

 
Answer"iframe" and "frame" are the reason. Pin
Member 802713129-Jun-11 5:14
Member 802713129-Jun-11 5:14 
GeneralRe: But "search" and "checkbox" are not working Pin
Member 802713129-Jun-11 5:17
Member 802713129-Jun-11 5:17 
QuestionHow to set width & heigth Pin
Member 779872930-Mar-11 5:15
Member 779872930-Mar-11 5:15 
AnswerRe: How to set width & heigth Pin
Member 779872930-Mar-11 21:25
Member 779872930-Mar-11 21:25 
AnswerRe: How to set width & heigth Pin
Talha Ashfaque24-Sep-12 3:57
Talha Ashfaque24-Sep-12 3:57 
GeneralNice Work Pin
Shahib71330-Sep-10 10:17
Shahib71330-Sep-10 10:17 
GeneralMy vote of 4 Pin
manas sinha15-Jul-10 19:44
manas sinha15-Jul-10 19:44 
GeneralSupport for Firefox and Chrome PinPopular
Arne Møller4-Aug-09 4:51
Arne Møller4-Aug-09 4:51 
Hi,
I have made a couple of changes to make this excelent piece of code support Firefox and Chrome.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Fixed Headers</title>
<style type="text/css">
body, td, p {font-family: Verdana;font-size: 10pt;}
h1{font-family: Verdana;font-size: 14pt;}
table{border-collapse: collapse;}
td, th{border-top: solid 1px #666666;border-bottom: solid 1px #666666;white-space: nowrap;padding-left: 10px;padding-right: 10px;text-align: left;}
th{background-color: #666666;color: #ffffff;}
#outerDiv{position: relative;}
#innerDiv{overflow: auto;}
#innerDiv td{white-space: nowrap;}
</style>
<script type="text/javascript">
var divContent = null;
var divTbl = null;
var divHeaderRow = null;
var divHeaderColumn = null;
var divHeaderRowColumn = null;
var headerRowFirstColumn = null;
var x;
var y;
var horizontal = false;
var vertical = false;

// Code inspiration "from http://stackoverflow.com/questions/920478/javascript-traversing-the-html-dom-using-childnodes-causes-errors-in-non-ie-bro"
function child(elem, index) {
// if index is not supplied, default is 1
// you might be more comfortable making this 0-based
// in which case change i initial assignment value to 0 too
index = index || 1;
// get first child element node of elem
elem = (elem.firstChild && elem.firstChild.nodeType != 1) ?
next(elem.firstChild) :
elem.firstChild;
// use the index to move to nth-child element node
for(var i=1; i < index;i++) {
(function() {
return elem = next(elem);
})();
}
return elem;
}

function next(elem) {
do {
elem = elem.nextSibling;
} while (elem && elem.nodeType != 1);
return elem;
}
// end inspiration

// Copy table to top and to left
function CreateScrollHeader(content, scrollHorizontal, scrollVertical)
{
horizontal = scrollHorizontal;
vertical = scrollVertical;

if (content != null)
{
divContent = content;

divTbl=child(divContent);
var headerRow = child(child(divTbl));

x = divTbl.offsetWidth;
y = divTbl.offsetHeight;

divHeaderRow = divContent.cloneNode(true);
if (horizontal)
{
divHeaderRow.style.height = headerRow.offsetHeight + "px";
divHeaderRow.style.overflow = "hidden";

divContent.parentNode.insertBefore(divHeaderRow, divContent);
divTbl.style.position = "absolute";
divTbl.style.top = "-" + headerRow.offsetHeight + "px";

y = y - headerRow.offsetHeight;
}

divHeaderRowColumn = divHeaderRow.cloneNode(true);
headerRowFirstColumn = child(headerRow);
divHeaderColumn = divContent.cloneNode(true);
divContent.style.position = "relative";

if (vertical)
{
divContent.parentNode.insertBefore(divHeaderColumn, divContent);
divContent.style.left = headerRowFirstColumn.offsetWidth + "px";

divTbl.style.position = "absolute";
divTbl.style.left = "-" + headerRowFirstColumn.offsetWidth + "px";
}
else
{
divContent.style.left = 0 + "px";
}

if (vertical)
{
divHeaderColumn.style.width = headerRowFirstColumn.offsetWidth + "px";
divHeaderColumn.style.overflow = "hidden";
divHeaderColumn.style.zIndex = "99";

divHeaderColumn.style.position = "absolute";
divHeaderColumn.style.left = "0" + "px";
addScrollSynchronization(divHeaderColumn, divContent, "vertical");
x = x - headerRowFirstColumn.offsetWidth;
}

if (horizontal)
{
if (vertical)
{
divContent.parentNode.insertBefore(divHeaderRowColumn, divContent);
}
divHeaderRowColumn.style.position = "absolute";
divHeaderRowColumn.style.left = "0" + "px";
divHeaderRowColumn.style.top = "0" + "px";
divHeaderRowColumn.style.width = headerRowFirstColumn.offsetWidth + "px";
divHeaderRowColumn.overflow = "hidden";
divHeaderRowColumn.style.zIndex = "100";
divHeaderRowColumn.style.backgroundColor = "#ffffff";

}

if (horizontal)
{ addScrollSynchronization(divHeaderRow, divContent, "horizontal");
}

if (horizontal || vertical)
{
window.onresize = ResizeScrollArea;
ResizeScrollArea();
}
}
}


// Resize scroll area to window size.
function ResizeScrollArea()
{ var height = document.documentElement.clientHeight - 120;
if (!vertical)
{
height -= divHeaderRow.offsetHeight;
}
var width = document.documentElement.clientWidth - 50;
if (!horizontal)
{
width -= divHeaderColumn.offsetWidth;
}
var headerRowsWidth = 0;
divTbl.style.width = x + "px";
divTbl.style.height = y + "px";

if (divHeaderRowColumn != null)
{
headerRowsWidth = divHeaderRowColumn.offsetWidth;
}

// width
if (divTbl.offsetWidth > width)
{
divContent.style.width = Math.max(width - headerRowsWidth, 0) + "px";
divContent.style.overflowX = "scroll";
divContent.style.overflowY = "auto";
}
else
{
divContent.style.width = x + "px";
divContent.style.overflowX = "auto";
divContent.style.overflowY = "auto";
}

if (divHeaderRow != null)
{
divHeaderRow.style.width = divContent.offsetWidth + headerRowsWidth + "px";
}

// height
if (divTbl.offsetHeight > height)
{
divContent.style.height = Math.max(height, 80) + "px";
divContent.style.overflowY = "scroll";
}
else
{
divContent.style.height = y + "px";
divContent.style.overflowY = "hidden";
}
if (divHeaderColumn != null)
{
divHeaderColumn.style.height = divContent.offsetHeight + "px";
}

// check scrollbars
if (divContent.style.overflowY == "scroll")
{
divContent.style.width = divContent.offsetWidth + 17 + "px";
}
if (divContent.style.overflowX == "scroll")
{
divContent.style.height = divContent.offsetHeight + 17 + "px";
}

// divContent.parentElement.style.width = divContent.offsetWidth + headerRowsWidth;
divContent.parentNode.style.width = divContent.offsetWidth + headerRowsWidth + "px";
}


// ********************************************************************************
// Synchronize div elements when scrolling
// from http://webfx.eae.net/dhtml/syncscroll/syncscroll.html
// ********************************************************************************
// This is a function that returns a function that is used
// in the event listener
function getOnScrollFunction(oElement) {
return function () {
if (oElement._scrollSyncDirection == "horizontal" || oElement._scrollSyncDirection == "both")
oElement.scrollLeft = event.srcElement.scrollLeft;
if (oElement._scrollSyncDirection == "vertical" || oElement._scrollSyncDirection == "both")
oElement.scrollTop = event.srcElement.scrollTop;
};

}

// This function adds scroll syncronization for the fromElement to the toElement
// this means that the fromElement will be updated when the toElement is scrolled
function addScrollSynchronization(fromElement, toElement, direction) {
removeScrollSynchronization(fromElement);

fromElement._syncScroll = getOnScrollFunction(fromElement);
fromElement._scrollSyncDirection = direction;
fromElement._syncTo = toElement;
if(window.addEventListener)
{ toElement.addEventListener("onscroll", fromElement._syncScroll, false);
}
else
toElement.attachEvent("onscroll", fromElement._syncScroll);
}

// removes the scroll synchronization for an element
function removeScrollSynchronization(fromElement) {
if (fromElement._syncTo != null)
{ if(window.addEventListener)
{ toElement.addEventListener("onscroll", fromElement._syncScroll, false);
}
else
fromElement._syncTo.detachEvent("onscroll", fromElement._syncScroll);
}

fromElement._syncTo = null;
fromElement._syncScroll = null;
fromElement._scrollSyncDirection = null;
}
</script>
</head>
<body>
<h1>Fixed Headers</h1>
<div id="outerDiv">
<div id="innerDiv">
<table>
<tr id="HeaderRow">
<th>Table</th>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
<th>Column 4</th>
<th>Column 5</th>
<th>Column 6</th>
<th>Column 7</th>
<th>Column 8</th>
<th>Column 9</th>
<th>Column 10</th>
<th>Column 11</th>
<th>Column 12</th>
</tr>
<tr>
<th>Row 1</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 2</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 3</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 4</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 5</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 6</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 7</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 8</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 9</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 10</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 11</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 12</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 13</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 14</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 15</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 16</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 17</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 18</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 19</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
<tr>
<th>Row 20</th>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Value 4</td>
<td>Value 5</td>
<td>Value 6</td>
<td>Value 7</td>
<td>Value 8</td>
<td>Value 9</td>
<td>Value 10</td>
<td>Value 11</td>
<td>Value 12</td>
</tr>
</table>
</div>
</div>
</body>
</html>
<script language="javascript">
CreateScrollHeader(document.getElementById("innerDiv"), true, true);
</script>

Kind Regards
Arne Møller
GeneralRe: Support for Firefox and Chrome [modified] Pin
Keith Worden9-Jul-10 4:56
Keith Worden9-Jul-10 4:56 
GeneralFreezing multiple columns PinPopular
Søren Christensen22-Apr-09 22:02
Søren Christensen22-Apr-09 22:02 
GeneralRe: Freezing multiple columns Pin
oxid2-Jun-09 22:51
oxid2-Jun-09 22:51 
GeneralRe: Freezing multiple columns Pin
cyguard26-Aug-09 5:05
cyguard26-Aug-09 5:05 
QuestionDoes anyone have a demo posted somewhere that works on Firefox & Safari? Pin
tactics233710-Apr-09 9:05
tactics233710-Apr-09 9:05 
AnswerRe: Does anyone have a demo posted somewhere that works on Firefox & Safari? Pin
tactics233710-Apr-09 12:08
tactics233710-Apr-09 12:08 
GeneralRe: Does anyone have a demo posted somewhere that works on Firefox & Safari? Pin
stuti IT8-Dec-10 2:38
stuti IT8-Dec-10 2:38 
GeneralNot working Pin
saulo benigno28-Jan-09 8:31
saulo benigno28-Jan-09 8:31 
QuestionEvents not firing with fixed columns [modified] Pin
srinian30-Sep-08 11:48
srinian30-Sep-08 11:48 
AnswerRe: Events not firing with fixed columns Pin
Member 796008327-May-11 6:35
Member 796008327-May-11 6:35 
GeneralRe: Events not firing with fixed columns Pin
Member 291225510-Apr-15 11:45
Member 291225510-Apr-15 11:45 
GeneralCheckboxes in Scrollable Table Pin
kusseal3-May-07 22:01
kusseal3-May-07 22:01 
GeneralRe: Checkboxes in Scrollable Table Pin
dubbele onzin29-Oct-07 9:59
dubbele onzin29-Oct-07 9:59 
GeneralRe: Checkboxes in Scrollable Table Pin
Member 1018685024-Nov-14 20:31
Member 1018685024-Nov-14 20:31 
JokeFreezing Multiple Columns Pin
sivad117825-Oct-06 14:14
sivad117825-Oct-06 14:14 
GeneralRe: Freezing Multiple Columns Pin
Hayw1r326-Nov-07 12:47
Hayw1r326-Nov-07 12:47 
GeneralRe: Freezing Multiple Columns Pin
pete in atlanta17-Dec-07 2:24
pete in atlanta17-Dec-07 2:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.