Fix thead on page scroll

Here's something that kind of works (quickly tested in FF 3.5, Chrome 3, and IE 8) that you might be able to tailor to your needs.

It uses absolute positioning of the thead. When the thead becomes absolutely positioned, this basically removes it from the original flow of the table and the widths of all the tbody tds adjust accordingly. So you get ugly behavior if you don't specify column widths or if any column's header is wider than any other cell in that column.

CSS

#Grid thead.Fixed
{
    position: absolute;
}

HTML

<table id="Grid">
    <thead>
        <tr>
            <td>A</td>
            <td>B</td>
            <td>C</td>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1</td>
            <td>2</td>
            <td>3</td>
        </tr>
        <!-- etc. -->
    </tbody>
</table>

jQuery

$(function() {
    var table = $("#Grid");

    $(window).scroll(function() {
        var windowTop = $(window).scrollTop();

        if (windowTop > table.offset().top) {
            $("thead", table).addClass("Fixed").css("top", windowTop);
        }
        else {
            $("thead", table).removeClass("Fixed");
        }
    });
});

I noticed that it does not work in IE unless you specify a strict XHTML doctype:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
    <!-- etc. -->

You can use Lobstrosity's code with a slight modification: position: fixed instead of absolute.

position: fixed is now widely adopted by all browsers including IE8 and onwards. BTW fixed renders much nicer on mobile/tablet devices than position: absolute.

I found that on a table with dynamic widths for each column, the absolutely positioned <thead> would lose the widths of the rest of the columns, so to fix this I came up with the following code:

What this code does is as follows:

Determines the widths of each column in your table by looking up the CSS widths of the first <tbody> <tr> <td> row and storing these in an array for later. When the user scrolls the class 'fixed' is added to the <thead> (Default browser behaviour will alter the widths of the <th>'s and they won't match up with the <tbody>. So to fix this we retroactively set the widths of the <th> to the values we've read earlier.

Anyway here's the code:

CSS

table.entries {width: 100%;border-spacing: 0px;margin:0;}
table.entries thead.fixed {position:fixed;top:0;}

HTML

<table class="entries" id="entriestable">
    <thead>
        <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Location</th>
            <th>DOB</th>
            <th>Opt&nbsp;in</th>
            <th>Added</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td class="name">Ricky Bobby</td>
            <td>[email protected]</td>
            <td class="addy"><i>Kent, GB</i></td>
            <td class="dob">20/08/1984</td>
            <td>Yes</td>
            <td class="date">4 hours ago</td>
        </tr>
    </tbody>
</table>

JavaScript

TableThing = function(params) {
    settings = {
        table: $('#entriestable'),
        thead: []
    };

    this.fixThead = function() {
        // empty our array to begin with
        settings.thead = [];
        // loop over the first row of td's in &lt;tbody> and get the widths of individual &lt;td>'s
        $('tbody tr:eq(1) td', settings.table).each( function(i,v){
            settings.thead.push($(v).width());
        });

        // now loop over our array setting the widths we've got to the &lt;th>'s
        for(i=0;i<settings.thead.length;i++) {
            $('thead th:eq('+i+')', settings.table).width(settings.thead[i]);
        }

        // here we attach to the scroll, adding the class 'fixed' to the &lt;thead> 
        $(window).scroll(function() {
            var windowTop = $(window).scrollTop();

            if (windowTop > settings.table.offset().top) {
                $("thead", settings.table).addClass("fixed");
            }
            else {
                $("thead", settings.table).removeClass("fixed");
            }
        });
    }
}
$(function(){
    var table = new TableThing();
    table.fixThead();
    $(window).resize(function(){
        table.fixThead();
    });
});

(function ($) {
    $.fn.fixedHeader = function () {
        return this.filter('table').each(function () {
            var that = this;
            var $head = $('<div></div>').addClass('head');
            $(this).before($head);
            $('th', this).each(function () {
                var $el = $(this);
                var $div = $('<div></div>').width($el.outerWidth()).text(
                $el.text());
                $head.append($div);
            });
            $(window).on('scroll', function () {
                var $that = $(that);
                var w = $(window).scrollTop();
                var o = $('th', that).first().offset().top;
                var tableO = $that.offset().top + $that.height();
                if (o < w && tableO > w) {
                    $head.show();
                } else {
                    $head.hide();
                }
            });
        });
    };
})(jQuery);

You can use this jquery plugin (you need to adapt .head style to your thead style). Working example here: http://jsfiddle.net/lukasi/7K52p/1/

Tags:

Jquery