Skip to content

Instantly share code, notes, and snippets.

@i-scorpion
Created June 18, 2012 12:24
Show Gist options
  • 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);
@rafaelcardoso
Copy link

Thank you. This plugin will help me a lot.

@BrainCrumbz
Copy link

THIS
IS
SUPERB!!!

@azhurb
Copy link

azhurb commented Mar 14, 2013

Thanks!

@Neokris
Copy link

Neokris commented Mar 20, 2013

Hi !
Thank you for this amazing plug-in which work very well ! I've a question : I use Ajax to create dynamically my table and then i call this plug-in to create the fixed header. How to destroy a fixed header if it already created ?

Thanks for your answer !

@scabbiaza
Copy link

Plugin has one problem:
when the width of the columns changes, the width of the column float-header remains unchanged

For example, when you change the width of the browser window
Or when added to the table new lines (on ajax), and the width of the columns changes slightly

I see the decision to add an event handler to change the width of columns in a table,
but it was necessary to add plugin jquery.ba-resize.min.js and make some changes to bootstrap-table-fixed-header.js

var resizeHead = function(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 });
  }

  resizeHead(o);
  $(o.find('thead.header > tr:first > th')).resize(function(e){
   resizeHead(o);
  });

Maybe you know, how it's make better

@balaclark
Copy link

Heres a much more simple way to keep the fixed header the same width as the original table:

$(window).on('resize', function () {
    $('.header-copy').width($('.table-fixed-header').width())
});

Copy link

ghost commented Jun 13, 2013

Fixed Header bar is always displaying on top of the window, what should i do if i need to wrap in a div having class name table box.

Problem: The div having class name table box is in middle of the window. if i trying to scoll the fixed bar is displaying on the top of the window. I want it to be displayed it in the box named table box.

Please do the Needful.

@straydogstudio
Copy link

One other approach is to use wrapping div's to absolutely position the fixed header at the top, while the table scrolls inside the div with a hidden overflow. The header will never float off the table, but, then you have hidden content, which may not be desired.

https://gist.github.com/straydogstudio/5921932 (two forks off this gist)

Because this code uses a duplicate table with the same classes, duplicate styling for the header should not be needed.

Thanks for the code by the way.

@yyxy
Copy link

yyxy commented Aug 19, 2013

There is a bug that the header-copy thead always displays at the bottom of the table under the IE7.(also IE6)

@ZhukV
Copy link

ZhukV commented Sep 26, 2013

Thank. This is a good solution!

@viduz
Copy link

viduz commented Oct 22, 2013

Thank you for grate plugin.
I'm facing a problem in my MVC 3 application when I'm using this.

At one point the headers position changes ( to left) when scrolling down. It will be coming back to same position when continuing the scrolling but is there any way to fix it.

http://sdrv.ms/1caU6oK

@bobdetemple
Copy link

thanks for the plugin, best one I found to work with bootstrap.

@vkorichkov
Copy link

o.find('thead.header > tr:first > th').each(function (i, h){
    ww.push($(h).outerWidth(true));
});
....
o.find('thead.header-copy').css({ margin:'0 auto',
                                               'margin-left': '-1px',
                                               width: o.outerWidth(),
                                               'background-color':config.bgColor });

@just-boris
Copy link

updated example in my fork, now you may see it in action http://bl.ocks.org/just-boris/raw/7427348/

@reinout
Copy link

reinout commented Jan 1, 2014

@vkorichov: your autoWidth(true) fix gives me much better results than the original width().

I'm using twitter bootstrap's table-condensed class and my headers sometimes have a few words in them instead of just one short word. Perhaps that's the reason why the original doesn't work well for me?

Anyway, your fix is a good one.

@walbinjr
Copy link

Thanks just-boris, your example clarified me!

@arothenberg
Copy link

Great job.

Also for me:
reinout commented 6 months ago
@vkorichov: your autoWidth(true) fix gives me much better results than the original width().

@scotself
Copy link

scotself commented Sep 1, 2014

+1 for just-boris' change to autoWidth(true)

@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