Skip to content

Instantly share code, notes, and snippets.

@i-scorpion
Created June 18, 2012 12:24
Show Gist options
  • Star 78 You must be signed in to star a gist
  • Fork 34 You must be signed in to fork a gist
  • Save i-scorpion/2948136 to your computer and use it in GitHub Desktop.
Save i-scorpion/2948136 to your computer and use it in GitHub Desktop.
Twitter bootstrap fixed table header using jQuery
Here is a simple jQuery plugin to make a table header fixed on top when window is scrolled.
Using the code from twitter bootstrap documentation page, this code is customized for table header.
Create the table with following layout -
<table class="table-fixed-header">
<thead class="header">
<tr>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data Column 1</td>
<td>Data Column 2</td>
<td>Data Column 3</td>
</tr>
...
</tbody>
</table>
Add the css from the file "table-fixed-header.css"
To apply it to above table, call -
$('.table-fixed-header').fixedHeader();
It can take two parameters -
bgColor - background color of the fixed header
topOffset - offset from top of windows where the fixed-header will be positioned (default is 40)
If you use any other value for topOffset, update the "top" parameter CSS as well accordingly.
For example, if there is no fixed navbar, you can set topOffset to 0 -
$('.table-fixed-header').fixedHeader({topOffset: 0});
Also update the CSS to - top: 0;
<html lang="en">
<head>
<meta charset="utf-8">
<title>Table Fixed Header</title>
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css">
<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<!-- CSS and JS for table fixed header -->
<link rel="stylesheet" href="table-fixed-header.css">
<script src="table-fixed-header.js"></script>
<style type="text/css">
#mynav {
top: 0;
position: fixed;
left: 0;
right: 0;
margin: 0 auto;
z-index: 1030;
height:40px;
color:#fff;
background-color:#666;
text-align: center;
}
body { padding-top:60px; }
</style>
</head>
<body>
<div class="container">
<h2 id="mynav">| my navigation bar | my navigation bar | my navigation bar | my navigation bar |</h2>
<h1>Table Fixed Header</h1>
<table id="mytable" class="table table-bordered table-striped table-fixed-header">
<thead class="header">
<tr>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data Column 1</td>
<td>Data Column 2</td>
<td>Data Column 3</td>
</tr>
</tbody>
</table>
<h1>Another table</h1>
<table id="mytable2" class="table table-bordered table-striped table-fixed-header">
<thead class="header">
<tr style="color:#00f;">
<th>Column 11</th>
<th>Column 22</th>
<th>Column 33</th>
<th>Column 44</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data Column 11</td>
<td>Data Column 22</td>
<td>Data Column 33</td>
<td>Data Column 44</td>
</tr>
</tbody>
</table>
</div>
<script language="javascript" type="text/javascript" >
$(document).ready(function(){
// add 30 more rows to the table
var row = $('table#mytable > tbody > tr:first');
var row2 = $('table#mytable2 > tbody > tr:first');
for (i=0; i<30; i++) {
$('table#mytable > tbody').append(row.clone());
$('table#mytable2 > tbody').append(row2.clone());
}
// make the header fixed on scroll
$('.table-fixed-header').fixedHeader();
});
</script>
</body>
</html>
table .header-fixed {
position: fixed;
top: 40px;
z-index: 1020; /* 10 less than .navbar-fixed to prevent any overlap */
border-bottom: 1px solid #d5d5d5;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
-webkit-box-shadow: inset 0 1px 0 #fff, 0 1px 5px rgba(0,0,0,.1);
-moz-box-shadow: inset 0 1px 0 #fff, 0 1px 5px rgba(0,0,0,.1);
box-shadow: inset 0 1px 0 #fff, 0 1px 5px rgba(0,0,0,.1);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */
}
(function($) {
$.fn.fixedHeader = function (options) {
var config = {
topOffset: 40,
bgColor: '#EEEEEE'
};
if (options){ $.extend(config, options); }
return this.each( function() {
var o = $(this);
var $win = $(window)
, $head = $('thead.header', o)
, isFixed = 0;
var headTop = $head.length && $head.offset().top - config.topOffset;
function processScroll() {
if (!o.is(':visible')) return;
var i, scrollTop = $win.scrollTop();
var t = $head.length && $head.offset().top - config.topOffset;
if (!isFixed && headTop != t) { headTop = t; }
if (scrollTop >= headTop && !isFixed) { isFixed = 1; }
else if (scrollTop <= headTop && isFixed) { isFixed = 0; }
isFixed ? $('thead.header-copy', o).removeClass('hide')
: $('thead.header-copy', o).addClass('hide');
}
$win.on('scroll', processScroll);
// hack sad times - holdover until rewrite for 2.1
$head.on('click', function () {
if (!isFixed) setTimeout(function () { $win.scrollTop($win.scrollTop() - 47) }, 10);
})
$head.clone().removeClass('header').addClass('header-copy header-fixed').appendTo(o);
var ww = [];
o.find('thead.header > tr:first > th').each(function (i, h){
ww.push($(h).width());
});
$.each(ww, function (i, w){
o.find('thead.header > tr > th:eq('+i+'), thead.header-copy > tr > th:eq('+i+')').css({width: w});
});
o.find('thead.header-copy').css({ margin:'0 auto',
width: o.width(),
'background-color':config.bgColor });
processScroll();
});
};
})(jQuery);
@gudddu
Copy link

gudddu commented Sep 9, 2014

This works awesome. Thanks for all the hard work folks. I just have one small issue if someone can help me? I have a drop down menu (select list) just above the table and the drop down list gets hidden behind the Table Row. Is there a way I could have my Drop Down not get hidden behind?

@detoro84
Copy link

Hey, I have a problem with large horizontal tables. What about scrolling right or left? I just want the header to be fixed vertically, not horizontally.

Any idea?

@0x3333
Copy link

0x3333 commented Feb 20, 2015

@Tristanlogd
Copy link

Having an issue in Safari on Mac OSX. When a table goes off the page horizontally the headers are no longer lined up correctly. Otherwise, they show fine. Any ides what may be causing this?

@pjaiswal1985
Copy link

This jquery plugin fixed the width calculation issue.My page width is 'auto' and width of my page as well as header is nearly 1500px. Therefore I have horizontal scroll bar for it. But this fix has freezed the header at the top and when I scroll horizontally it remain frizzed and hides some of the header rows. Please help.

@yanickrochon
Copy link

Thank you for this code! I found, however, that it leaked memory if the table is being replaced often, as it is the case in single page apps, for example. So I rewrote it a little, adding the resize support also. Since I am using Bootstrap 3, the .width() function was not enough, so I changed it to .outerWidth() and everything is fine.

The main difference with this version is the thead class name, and the optional data-fixed-header-* attributes.

<table data-fixed-header-top-offset="100" data-fixed-header-bg-color="white">
  <thaad class="fixed-header">
    <th>...</th>
   ....
  </thead>
  <tbody>
    ...
  </tbody>
  ...
</table>

Note: if the table has the CSS class table-fixed-header, then no JS is required at all, and it will automatically be picked up by the plugin.

define([
  'jquery'
], function ($) {

var baseConfig = {
  topOffset: 40,
  bgColor: '#EEEEEE'
};

var $win;

function processResizeColumns(table, header) {
  var headerCopy = table.find('thead.fixed-header-copy > tr:first')
    .css('width', table.outerWidth())
  ;

  $('tr:first > th', header).each(function (i, h){
    headerCopy.find('th:eq('+i+')').css('width', $(h).outerWidth() );
  });
}

function processScroll(table, header) {
  var scrollTop;
  var headTop;
  var isFixed;

  if (!table.is(':visible')) {
    return;
  }

  isFixed = header.data('isFixed');
  headTop = header.length && header.offset().top - header.data('topOffset');
  scrollTop = $win.scrollTop();

  if (scrollTop >= headTop && !isFixed) {
    isFixed = 1;
  } else if (scrollTop <= headTop && isFixed) { 
    isFixed = 0;
  }

  isFixed ? $('thead.fixed-header-copy', table).removeClass('hide')
          : $('thead.fixed-header-copy', table).addClass('hide');

  header.data('isFixed', isFixed);
}


$.fn.fixedHeader = function (options) {
  options = $.extend({}, baseConfig, options || {});

  return this.each( function() {
    var table = $(this);
    var header = $('thead.fixed-header', table);
    var config;

    if (table.find('thead.fixed-header-copy').length || !header.length) {
      return;
    }

    table.addClass('table-fixed-header');

    config = $.extend({
      topOffset: table.data('fixed-header-top-offset'),
      bgColor: table.data('fixed-header-bg-color')
    }, options);

    // hack sad times - holdover until rewrite for 2.1
    header.on('click', function () {
      if (!header.data('isFixed')) {
        setTimeout(function () {
          $win.scrollTop($win.scrollTop() - 47); // not sure what this is for (yr)
        }, 10);
      }        
    }).data('topOffset', config.topOffset);

    header.clone()
      .removeClass('fixed-header')
      .addClass('fixed-header-copy')
      .css({
        position: 'fixed',
        margin:'0 auto',
        top: config.topOffset + 'px',
        backgroundColor: config.bgColor,
        zIndex:  1020    /* 10 less than .navbar-fixed to prevent any overlap */
      })
      .appendTo(table);

    processResizeColumns(table, header);
    processScroll(table, header);
  });
};


$(function () {
  $win = $(window).on('resize', function () {
    $('table.table-fixed-header').each(function () {
      processResizeColumns($(this), $('thead.fixed-header', this));
    });
  }).on('scroll', function () {
    $('table.table-fixed-header').each(function () {
      processScroll($(this), $('thead.fixed-header', this));
    });
  });

  // auto set on domcument ready
  $('table.table-fixed-header').fixedHeader();
});

});

Note: no CSS is required.

@yanickrochon
Copy link

@Tristanlogd your issue is that the original code sets the header to the table width on initialize. When the windows goes small, the width of the columns are set smaller, therefore no problem. But when the window size is resized larger (after init), then the columns's width do not go beyound the table header width previously set. My last snippet fixes this.

@momentmaker
Copy link

Works great. Thanks 😀

@alexdong
Copy link

Thumbup to @vkorichkov's OuterWidth fixed.

Copy link

ghost commented Feb 20, 2017

It's working perfectly on Chrome but not on Safari. :(

@Magniveo
Copy link

Magniveo commented Apr 11, 2018

Don't work for me.

@andrealuciani
Copy link

@yanickrochon

But when the window size is resized larger (after init), then the columns's width do not go beyound the table header width previously set. My last snippet fixes this.

Can you post a sample code please? (jquery newbie here)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment