ChatGPT解决这个技术问题 Extra ChatGPT

Table header to stay fixed at the top when user scrolls it out of view with jQuery

I am trying to design an HTML table where the header will stay at the top of the page when AND ONLY when the user scrolls it out of view. For example, the table may be 500 pixels down from the page, how do I make it so that if the user scrolls the header out of view (browser detects its no longer in the windows view somehow), it will stay put at the top? Anyone can give me a Javascript solution to this?

<table>
  <thead>
    <tr>
      <th>Col1</th>
      <th>Col2</th>
      <th>Col3</th>
    </tr>
  </thead>
  <tbody>
     <tr>
       <td>info</td>
       <td>info</td>
       <td>info</td>
     </tr>
     <tr>
       <td>info</td>
       <td>info</td>
       <td>info</td>
     </tr>
     <tr>
       <td>info</td>
       <td>info</td>
       <td>info</td>
     </tr>
  </tbody>
</table>

So in the above example, I want the <thead> to scroll with the page if it goes out of view.

IMPORTANT: I am NOT looking for a solution where the <tbody> will have a scrollbar (overflow:auto).


A
Andrew Whitaker

You would do something like this by tapping into the scroll event handler on window, and using another table with a fixed position to show the header at the top of the page.

HTML:

<table id="header-fixed"></table>

CSS:

#header-fixed {
    position: fixed;
    top: 0px; display:none;
    background-color:white;
}

JavaScript:

var tableOffset = $("#table-1").offset().top;
var $header = $("#table-1 > thead").clone();
var $fixedHeader = $("#header-fixed").append($header);

$(window).bind("scroll", function() {
    var offset = $(this).scrollTop();

    if (offset >= tableOffset && $fixedHeader.is(":hidden")) {
        $fixedHeader.show();
    }
    else if (offset < tableOffset) {
        $fixedHeader.hide();
    }
});

This will show the table head when the user scrolls down far enough to hide the original table head. It will hide again when the user has scrolled the page up far enough again.

Working example: http://jsfiddle.net/andrewwhitaker/fj8wM/


I was thinking of this but what if the header columns' widths change based on the content? Unless the column widths are fixed you could have your header row not line up with the content rows. Just a thought.
this example doesn't work if the column headers' names are shorter than the column data values. Try changing that fiddle to have values like infoooooooo instead of info. I'm thinking some sort of hardcoded width that's set when cloning the table.
This has major limitations, least of which is when trying to use the table in a container other than window. mkoryak.github.io/floatThead has a more generally-applicable solution.
This might help anyone needing dynamic th widths, it what I wound up doing, it's a fork of @AndrewWhitaker: jsfiddle.net/noahkoch/wLcjh/1
@NoahKoch fixed header using your solution can be still not as wide as original head when original cells have padding. I improved your solution to add padding to duplicated cells. JSFiddle's fork: jsfiddle.net/1n23m69u/2
I
Ihor Zenich

Pure CSS (without IE11 support):

table th {
    position: -webkit-sticky; // this is for all Safari (Desktop & iOS), not for Chrome
    position: sticky;
    top: 0;
    z-index: 1; // any positive value, layer order is global
    background: #fff; // any bg-color to overlap
}

I don't even see this working in Chrome. The inspection tools show -webkit-sticky as an invalid value for position.
@carmenism -webkit-sticky is for Safari (Desktop & iOS), not for Chrome. position: sticky works in Chrome from version 56. You should set BOTH rules: -webkit-sticky and just sticky exactly in the same order as in my code example. Safari gets webkit-sticky and all other browsers overwrite it with sticky.
I was unable to get this to work. From a quick google it looks like position: sticky will not work with table elements.
applying to th worked for me: table thead tr th{...}
You have to apply it to the th because it won't work on thead nor tr
n
neubert

Well, I was trying to obtain the same effect without resorting to fixed size columns or having a fixed height for the entire table.

The solution I came up with is a hack. It consists of duplicating the entire table then hiding everything but the header, and making that have a fixed position.

HTML

<div id="table-container">
<table id="maintable">
    <thead>
        <tr>
            <th>Col1</th>
            <th>Col2</th>
            <th>Col3</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>info</td>
            <td>info</td>
            <td>info</td>
        </tr>
        <tr>
            <td>info</td>
            <td>info</td>
            <td>info</td>
        </tr>
        <tr>
            <td>info</td>
            <td>some really long line here instead</td>
            <td>info</td>
        </tr>
        <tr>
            <td>info</td>
            <td>info</td>
            <td>info</td>
        </tr>
                <tr>
            <td>info</td>
            <td>info</td>
            <td>info</td>
        </tr>
                <tr>
            <td>info</td>
            <td>info</td>
            <td>info</td>
        </tr>
        <tr>
            <td>info</td>
            <td>info</td>
            <td>info</td>
        </tr>
    </tbody>
</table>
<div id="bottom_anchor"></div>
</div>

CSS

body { height: 1000px; }
thead{
    background-color:white;
}

javascript

function moveScroll(){
    var scroll = $(window).scrollTop();
    var anchor_top = $("#maintable").offset().top;
    var anchor_bottom = $("#bottom_anchor").offset().top;
    if (scroll>anchor_top && scroll<anchor_bottom) {
    clone_table = $("#clone");
    if(clone_table.length == 0){
        clone_table = $("#maintable").clone();
        clone_table.attr('id', 'clone');
        clone_table.css({position:'fixed',
                 'pointer-events': 'none',
                 top:0});
        clone_table.width($("#maintable").width());
        $("#table-container").append(clone_table);
        $("#clone").css({visibility:'hidden'});
        $("#clone thead").css({'visibility':'visible','pointer-events':'auto'});
    }
    } else {
    $("#clone").remove();
    }
}
$(window).scroll(moveScroll); 

See here: http://jsfiddle.net/QHQGF/7/

Edit: updated the code so that the thead can receive pointer events(so buttons and links in the header still work). This fixes the problem reported by luhfluh and Joe M.

New jsfiddle here: http://jsfiddle.net/cjKEx/


I tried that first, the problem with that is that the size of the elements in the header depend on the contents of the table. If you have a row with big content the entire column, including the header will grow. If you clone just the thead you lose that information and you don't get the desired effect. See Yzmir Ramirez's comment on the accepted answer. This is the only way I found that works without fixed width columns. It isn't clean, I know, which is I way I said it was a hack.
Oh, I got it. This method is great because it works with fluid columns, I googled a little and couldn't find another one with that functionality. Anywhay, if i'll get it done somehow, I'll mention it here
@luhfluh, yeah, the problem was due to the pointer-events:none in the cloned table. This is used so that clicks go through the clone and to the original table. Reverting it in the header of the cloned table so that the header(and only the header) is clickable fixes the problem. I've updated my post to reflect this :)
problem adding border on
This solutions worked for me, one small problem is if the table extends to the right past the screen. If the user scrolls right, the header does not scroll. Otherwise good.
m
mkoryak

I wrote a plugin that does this. Ive been working on it for about a year now and I think it handles all the corner cases pretty well:

scrolling within a container with overflow

scrolling within a window

taking care of what happens when you resize the window

keeping your events bound to the header

most importantly it doesn't force you to change your table's css to make it work

Here are some demos/docs:
http://mkoryak.github.io/floatThead/


looks nice but will not work if the table already runs other plugins (like in my case) wich conflict with the strict rules of your plugin
it works with any plugin that caches its selectors, which what good plugins should do. But no one does this any more because the young'ins never had to write any jquery plugins for IE6.
Works great, also good to mention is that it works very well with multidimensional tables! Only thing to point out is that when you call it to fast the table width get's a bit messed up. But that's easy to avoid with some callbacks.
This is awesome, saved me a lot of time, thank you!
r
rockusbacchus

I was able to fix the problem with changing column widths. I started with Andrew's solution above (thanks so much!) and then added one little loop to set the widths of the cloned td's:

$("#header-fixed td").each(function(index){
    var index2 = index;
    $(this).width(function(index2){
        return $("#table-1 td").eq(index).width();
    });
});

This solves the problem without having to clone the entire table and hide the body. I'm brand new to JavaScript and jQuery (and to stack overflow), so any comments are appreciated.


Worked well but I needed to add following line $("#header-fixed").width($("#table-1").width()); for columns to line up correctly.
j
josephting

This is by far the best solution I've found for having a fixed table header.

UPDATE 5/11: Fixed horizontal scrolling bug as pointed out by Kerry Johnson

Codepen: https://codepen.io/josephting/pen/demELL

;(function($) { $.fn.fixMe = function() { return this.each(function() { var $this = $(this), $t_fixed; function init() { $this.wrap('

'); $t_fixed = $this.clone(); $t_fixed.find("tbody").remove().end().addClass("fixed").insertBefore($this); resizeFixed(); } function resizeFixed() { $t_fixed.width($this.outerWidth()); $t_fixed.find("th").each(function(index) { $(this).css("width",$this.find("th").eq(index).outerWidth()+"px"); }); } function scrollFixed() { var offsetY = $(this).scrollTop(), offsetX = $(this).scrollLeft(), tableOffsetTop = $this.offset().top, tableOffsetBottom = tableOffsetTop + $this.height() - $this.find("thead").height(), tableOffsetLeft = $this.offset().left; if(offsetY < tableOffsetTop || offsetY > tableOffsetBottom) $t_fixed.hide(); else if(offsetY >= tableOffsetTop && offsetY <= tableOffsetBottom && $t_fixed.is(":hidden")) $t_fixed.show(); $t_fixed.css("left", tableOffsetLeft - offsetX + "px"); } $(window).resize(resizeFixed); $(window).scroll(scrollFixed); init(); }); }; })(jQuery); $(document).ready(function(){ $("table").fixMe(); $(".up").click(function() { $('html, body').animate({ scrollTop: 0 }, 2000); }); }); body{ font:1.2em normal Arial,sans-serif; color:#34495E; } h1{ text-align:center; text-transform:uppercase; letter-spacing:-2px; font-size:2.5em; margin:20px 0; } .container{ width:90%; margin:auto; } table{ border-collapse:collapse; width:100%; } .blue{ border:2px solid #1ABC9C; } .blue thead{ background:#1ABC9C; } .purple{ border:2px solid #9B59B6; } .purple thead{ background:#9B59B6; } thead{ color:white; } th,td{ text-align:center; padding:5px 0; } tbody tr:nth-child(even){ background:#ECF0F1; } tbody tr:hover{ background:#BDC3C7; color:#FFFFFF; } .fixed{ top:0; position:fixed; width:auto; display:none; border:none; } .scrollMore{ margin-top:600px; } .up{ cursor:pointer; }

↓ SCROLL ↓

Colonne 1 Colonne 2 Colonne 3
Non MaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !

↓ SCROLL MORE ↓

Colonne 1 Colonne 2 Colonne 3
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !
Non Mais Allo !

↑ UP ↑


I fixed this script so that it can handle <th> elements that have border and padding (otherwise it sets the fixed header <th> widths too wide). Simply change outerWidth to width in the resizeFixed() function definition.
You may also need to add a CSS property of z-index to some positive number on the .fixed class so that objects rendered post-pageload in the table don't float on top of the fixed header as you scroll.
how to dynamically handle the top position. Let say, I need to scroll upto below my main page fixed header content. .fixed{ top:0;}.
@VVijay You can always adjust the positioning of the fixed header. The idea here is to show the fixed copy of the table header when your page get scrolled to the point you set it to be. See scrollFixed() function.
@KerryJohnson Thanks for the note. I've made some changes so it supports horizontal scrolling and dynamic column widths.
S
Srivats Shankar

There are many really good solution here already. But one of the simplest CSS only solutions that I use in these situations is as follows:

table { /* Not required only for visualizing */ border-collapse: collapse; width: 100%; } table thead tr th { /* Important */ background-color: red; position: sticky; z-index: 100; top: 0; } td { /* Not required only for visualizing */ padding: 1em; }

Col1 Col2 Col3
info info info
info info info
info info info
info info info
info info info
info info info
info info info
info info info
info info info
info info info
info info info
info info info

Because there is no requirement for JavaScript it simplifies the situation significantly. You essentially need to focus on the second CSS rule, which contains the conditions for ensuring that the head of the table remains of the top no matter the scroll space.

To elaborate on each of the rules in detail. position is meant to indicate to the browser that the head object, its row, and its cells all need to stick to the top. This necessarily needs to be accompanied by top, which specifies to the browser that the head will stick to the top of the page or viewport. Additionally, you can add z-index to ensure that the content of the head always remains on the top.

The background colour is merely to illustrate the point. You do not need to use any additional JavaScript to get this effect. This is supported in most major browsers after 2016.


J
Janning

Best solution is to use this jquery plugin:

https://github.com/jmosbech/StickyTableHeaders

This plugin worked great for us and we tried a lot other solutions. We tested it in IE, Chrome and Firefox


Do you know any solution which also preserves the functionality of knockout data bound controls in the header?
Didn't work for me: headers are outside of table and stick to top of the browser (window), even with the option "scrollableArea". I saw that this was mentioned before without additional comments. Maybe it needs a CSS that he use and is not available to make it work.
A
Abhi Das

I found a simple solution without using JQuery and using CSS only.

You have to put the fixed contents inside 'th' tags and add the CSS

table th {
    position:sticky;
    top:0;
    z-index:1;
    border-top:0;
    background: #ededed;
}
   

The position, z-index and top properties are enough. But you can apply the rest to give for a better view.


How is this different from Ihor Zenich's answer, which was posted two years earlier?
L
Lok Yan Wong

I found a simple jQuery library called Sticky Table Headers. Two lines of code and it did exactly what I wanted. The solutions above don't manage the column widths, so if you have table cells that take up a lot of space, the resulting size of the persistent header will not match your table's width.

http://plugins.jquery.com/StickyTableHeaders/

Usage info here: https://github.com/jmosbech/StickyTableHeaders


x
xelilof

you can use this approach, pure HTML and CSS no JS needed :)

.table-fixed-header { display: flex; justify-content: space-between; margin-right: 18px } .table-fixed { display: flex; justify-content: space-between; height: 150px; overflow: scroll; } .column { flex-basis: 24%; border-radius: 5px; padding: 5px; text-align: center; } .column .title { border-bottom: 2px grey solid; border-top: 2px grey solid; text-align: center; display: block; font-weight: bold; } .cell { padding: 5px; border-right: 1px solid; border-left: 1px solid; } .cell:nth-of-type(even) { background-color: lightgrey; } Fixed header Bin

col 1
col 2
col 3
col 4
alpha
beta
ceta
alpha
beta
ceta
alpha
beta
ceta
alpha
beta
ceta
alpha
beta
ceta
beta
beta
beta
alpha
beta
ceta


s
seeit360

I too experienced the same issues with the border formatting not being shown using entrophy's code but a few little fixes and now the table is expandable and displays all css styling rules you may add.

to css add:

#maintable{width: 100%}    

then here is the new javascript:

    function moveScroll(){
    var scroll = $(window).scrollTop();
    var anchor_top = $("#maintable").offset().top;
    var anchor_bottom = $("#bottom_anchor").offset().top;
    if (scroll > anchor_top && scroll < anchor_bottom) {
        clone_table = $("#clone");
        if(clone_table.length === 0) {          
            clone_table = $("#maintable").clone();
            clone_table.attr({id: "clone"})
            .css({
                position: "fixed",
                "pointer-events": "none",
                 top:0
            })
            .width($("#maintable").width());

            $("#table-container").append(clone_table);
            // dont hide the whole table or you lose border style & 
            // actively match the inline width to the #maintable width if the 
            // container holding the table (window, iframe, div) changes width          
            $("#clone").width($("#maintable").width());
            // only the clone thead remains visible
            $("#clone thead").css({
                visibility:"visible"
            });
            // clone tbody is hidden
            $("#clone tbody").css({
                visibility:"hidden"
            });
            // add support for a tfoot element
            // and hide its cloned version too
            var footEl = $("#clone tfoot");
            if(footEl.length){
                footEl.css({
                    visibility:"hidden"
                });
            }
        }
    } 
    else {
        $("#clone").remove();
    }
}
$(window).scroll(moveScroll);

This does work better than entropy's solution as far as border formatting goes in the header. However, in current Firefox (but not Chrome) it creates visible cell-border artifacts below the table once scrolling starts.
This solution works. But when the windows is small, it the cloned header doesn't scroll horizontally, any suggestion to fix that?
B
Ben Gripka

Here is a solution that builds upon the accepted answer. It corrects for: column widths, matching table style, and when the table is scrolled in a container div.

Usage

Ensure your table has a <thead> tag because only thead content will be fixed.

$("#header-fixed").fixHeader();

JavaSript

//Custom JQuery Plugin
(function ($) {
    $.fn.fixHeader = function () {
        return this.each(function () {
            var $table = $(this);
            var $sp = $table.scrollParent();
            var tableOffset = $table.position().top;
            var $tableFixed = $("<table />")
                .prop('class', $table.prop('class'))
                .css({ position: "fixed", "table-layout": "fixed", display: "none", "margin-top": "0px" });
            $table.before($tableFixed);
            $tableFixed.append($table.find("thead").clone());

            $sp.bind("scroll", function () {
                var offset = $(this).scrollTop();

                if (offset > tableOffset && $tableFixed.is(":hidden")) {
                    $tableFixed.show();
                    var p = $table.position();
                    var offset = $sp.offset();

                    //Set the left and width to match the source table and the top to match the scroll parent
                    $tableFixed.css({ left: p.left + "px", top: (offset ? offset.top : 0) + "px", }).width($table.width());

                    //Set the width of each column to match the source table
                    $.each($table.find('th, td'), function (i, th) {
                        $($tableFixed.find('th, td')[i]).width($(th).width());
                    });

                }
                else if (offset <= tableOffset && !$tableFixed.is(":hidden")) {
                    $tableFixed.hide();
                }
            });
        });
    };
})(jQuery);

This worked well for me. Only thing is an issue with tables that need to scroll horizontally. I've made an update in a JSFiddle here jsfiddle.net/4mgneob1/1 I subtract $sp.scrollLeft() which gets the amount scrolled from left so when scrolling down the fixed table is positioned correctly. Also added check when scrolling horizontally if fixed table is visible to adjust left position. Another issue I couldn't solve is when the table goes above the viewport the fixed table should hide until the user scrolls back up.
Getting Uncaught TypeError: $table.scrollParent is not a function
A
Alexey Shmalko

This will help you to have a fixed header which can also be scrolled horizontally with data.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Shubh</title>



<script type="text/javascript">
    var lastSeen = [ 0, 0 ];
    function checkScroll(div1, div2) {
        if (!div1 || !div2)
            return;
        var control = null;
        if (div1.scrollLeft != lastSeen[0])
            control = div1;
        else if (div2.scrollLeft != lastSeen[1])
            control = div2;
        if (control == null)
            return;
        else
            div1.scrollLeft = div2.scrollLeft = control.scrollLeft;
        lastSeen[0] = div1.scrollLeft;
        lastSeen[1] = div2.scrollLeft;
    }

    window
            .setInterval(
                    "checkScroll(document.getElementById('innertablediv'), document.getElementById('headertable'))",
                    1);
</script>

<style type="text/css">
#full {
    width: 400px;
    height: 300px;
}

#innertablediv {
    height: 200px;
    overflow: auto;
}

#headertable {
    overflow: hidden;
}
</style>
</head>
<body>

    <div id="full">




        <div id="headertable">
            <table border="1" bgcolor="grey" width="150px" id="headertable">
                <tr>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>

                    <td>&nbsp;&nbsp;&nbsp;</td>
                </tr>

            </table>
        </div>




        <div id="innertablediv">

            <table border="1" id="innertableid">
                <tr>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                </tr>
                <tr>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                </tr>
                <tr>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                </tr>
                <tr>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                </tr>
                <tr>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                </tr>
                <tr>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                </tr>
                <tr>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                </tr>
                <tr>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                </tr>
                <tr>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                    <td>shubh, ansh</td>
                </tr>

            </table>
        </div>
    </div>
</body>
</html>

This will help you to have a fixed header which can also be scrolled horizontally with data.
J
JustWe
function fix_table_header_position(){
 var width_list = [];
 $("th").each(function(){
    width_list.push($(this).width());
 });
 $("tr:first").css("position", "absolute");
 $("tr:first").css("z-index", "1000");
 $("th, td").each(function(index){
    $(this).width(width_list[index]);
 });

 $("tr:first").after("<tr height=" + $("tr:first").height() + "></tr>");}

This is my solution


L
Leo Lloyd Andrade

A bit late to the party, but here is an implementation that works with multiple tables on the same page and "jank" free (using requestAnimationFrame). Also there's no need to provide any width on the columns. Horizontal scrolling works as well.

The headers are defined in a div so you are free to add any markup there (like buttons), if required. This is all the HTML that is needed:

<div class="tbl-resp">
  <table id="tbl1" class="tbl-resp__tbl">
     <thead>
      <tr>
        <th>col 1</th>
        <th>col 2</th>
        <th>col 3</th>
      </tr>
    </thead> 
  </table>
</div>

https://jsfiddle.net/lloydleo/bk5pt5gs/


P
Piotr C.

In this solution fixed header is created dynamically, the content and style is cloned from THEAD

all you need is two lines for example:

var $myfixedHeader = $("#Ttodo").FixedHeader(); //create fixed header $(window).scroll($myfixedHeader.moveScroll); //bind function to scroll event

My jquery plugin FixedHeader and getStyleObject provided below you can to put in the file .js

// JAVASCRIPT /* * getStyleObject Plugin for jQuery JavaScript Library * From: http://upshots.org/?p=112 Basic usage: $.fn.copyCSS = function(source){ var styles = $(source).getStyleObject(); this.css(styles); } */ (function($){ $.fn.getStyleObject = function(){ var dom = this.get(0); var style; var returns = {}; if(window.getComputedStyle){ var camelize = function(a,b){ return b.toUpperCase(); }; style = window.getComputedStyle(dom, null); for(var i = 0, l = style.length; i < l; i++){ var prop = style[i]; var camel = prop.replace(/\-([a-z])/g, camelize); var val = style.getPropertyValue(prop); returns[camel] = val; }; return returns; }; if(style = dom.currentStyle){ for(var prop in style){ returns[prop] = style[prop]; }; return returns; }; return this.css(); } })(jQuery); //Floating Header of long table PiotrC (function ( $ ) { var tableTop,tableBottom,ClnH; $.fn.FixedHeader = function(){ tableTop=this.offset().top, tableBottom=this.outerHeight()+tableTop; //Add Fixed header this.after('

'); //Clone Header ClnH=$("#fixH").html(this.find("thead").clone()); //set style ClnH.css({'position':'fixed', 'top':'0', 'zIndex':'60', 'display':'none', 'border-collapse': this.css('border-collapse'), 'border-spacing': this.css('border-spacing'), 'margin-left': this.css('margin-left'), 'width': this.css('width') }); //rewrite style cell of header $.each(this.find("thead>tr>th"), function(ind,val){ $(ClnH.find('tr>th')[ind]).css($(val).getStyleObject()); }); return ClnH;} $.fn.moveScroll=function(){ var offset = $(window).scrollTop(); if (offset > tableTop && offsettableBottom){ if(!ClnH.is(':hidden'))ClnH.hide(); } }; })( jQuery ); var $myfixedHeader = $("#repTb").FixedHeader(); $(window).scroll($myfixedHeader.moveScroll); /* CSS - important only NOT transparent background */ #repTB{border-collapse: separate;border-spacing: 0;} #repTb thead,#fixH thead{background: #e0e0e0 linear-gradient(#d8d8d8 0%, #e0e0e0 25%, #e0e0e0 75%, #d8d8d8 100%) repeat scroll 0 0;border:1px solid #CCCCCC;} #repTb td{border:1px solid black}

example

Col1Column2Description
infoinfoinfo
infoinfoinfo
infoinfoinfo
infoinfoinfo
infoinfoinfo
infoinfoinfo
infoinfoinfo
infoinfoinfo
infoinfoinfo
infoinfoinfo
infoinfoinfo


N
Novice_JS

Create extra table with same header as the main table. Just put thead in the new table with one row and all the headers in it. Do position absolute and background white. For main table put it in a div and use some height and overflow-y scroll. This way our new table will overcome the header of main table and stay there. Surround everything in a div. Below is the rough code to do it.

      <div class="col-sm-8">

        <table id="header-fixed" class="table table-bordered table-hover" style="width: 351px;position: absolute;background: white;">
        <thead>
        <tr>
            <th>Col1</th>
            <th>Col2</th>
            <th>Col3</th>
        </tr>
    </thead>
      </table>


    <div style="height: 300px;overflow-y: scroll;">
          <table id="tableMain" class="table table-bordered table-hover" style="table-layout:fixed;overflow-wrap: break-word;cursor:pointer">
<thead>
    <tr>
      <th>Col1</th>
      <th>Col2</th>
      <th>Col3</th>
    </tr>
  </thead>
  <tbody>
     <tr>
       <td>info</td>
       <td>info</td>
       <td>info</td>
     </tr>
     <tr>
       <td>info</td>
       <td>info</td>
       <td>info</td>
     </tr>
     <tr>
       <td>info</td>
       <td>info</td>
       <td>info</td>
     </tr>
  </tbody>

                                    </table>
              </div>
        </div>

r
revilodesign.de

div.wrapper { padding:20px; } table.scroll thead { width: 100%; background: #FC6822; } table.scroll thead tr:after { content: ''; overflow-y: scroll; visibility: hidden; } table.scroll thead th { flex: 1 auto; display: block; color: #fff; } table.scroll tbody { display: block; width: 100%; overflow-y: auto; height: auto; max-height: 200px; } table.scroll thead tr, table.scroll tbody tr { display: flex; } table.scroll tbody tr td { flex: 1 auto; word-wrap: break; } table.scroll thead tr th, table.scroll tbody tr td { width: 25%; padding: 5px; text-align-left; border-bottom: 1px solid rgba(0,0,0,0.3); }

Name Vorname Beruf Alter
Müller Marie Künstlerin 26
Meier Stefan Chemiker 52
Schmidt Sabrine Studentin 38
Mustermann Max Lehrer 41
Müller Marie Künstlerin 26
Meier Stefan Chemiker 52
Schmidt Sabrine Studentin 38
Mustermann Max Lehrer 41
Müller Marie Künstlerin 26
Meier Stefan Chemiker 52
Schmidt Sabrine Studentin 38
Mustermann Max Lehrer 41

Demo: css fixed table header demo


J
J Benjamín Samayoa

Fix your issue with this

tbody {
  display: table-caption;
  height: 200px;
  caption-side: bottom;
  overflow: auto;
}

S
Sandip Solanki

solution in my case, i have use only css for this:

first we nee table format like this :

<table>
<thead><tr><th></th></tr></thead>
<tbody><tr><td></td><tr></tbody>
</table>

And apply this css :

tbody {
    display:block;
    max-height:280px;
    overflow-x: hidden;
    overflow-y: auto;
    }
    thead, tbody tr {
        display:table;
        width:100%;
        table-layout:fixed;
    }

and header is fixed


D
Daniel Budzyński

This can be achieved by using style property transform. All you have to do is wrapping your table into some div with fixed height and overflow set to auto, for example:

.tableWrapper {
  overflow: auto;
  height: calc( 100% - 10rem );
}

And then you can attach onscroll handler to it, here you have method that finds each table wrapped with <div class="tableWrapper"></div>:

  fixTables () {
    document.querySelectorAll('.tableWrapper').forEach((tableWrapper) => {
      tableWrapper.addEventListener('scroll', () => {
        var translate = 'translate(0,' + tableWrapper.scrollTop + 'px)'
        tableWrapper.querySelector('thead').style.transform = translate
      })
    })
  }

And here is working example of this in action (i have used bootstrap to make it prettier): fiddle

For those who also want to support IE and Edge, here is the snippet:

  fixTables () {
    const tableWrappers = document.querySelectorAll('.tableWrapper')
    for (let i = 0, len = tableWrappers.length; i < len; i++) {
      tableWrappers[i].addEventListener('scroll', () => {
        const translate = 'translate(0,' + tableWrappers[i].scrollTop + 'px)'
        const headers = tableWrappers[i].querySelectorAll('thead th')
        for (let i = 0, len = headers.length; i < len; i++) {
          headers[i].style.transform = translate
        }
      })
    }
  }

In IE and Edge scroll is a little bit laggy... but it works

Here is answer which helps me to find out this: answer


J
Jeffrey Roosendaal

I've tried most of these solutions, and eventually found (IMO) the best, modern, solution:

CSS grids

With CSS grids, you can define a 'grid', and you can finally create a nice, javascript-free, cross-browser solution for a table with a fixed header, and scrollable content. The header height can even dynamic.

CSS: Display as grid, and set the number of template-rows:

.grid {
    display: grid;
    grid-template-rows: 50px auto; // For fixed height header
    grid-template-rows: auto auto; // For dynamic height header
}

HTML: Create a grid container and the number of defined rows:

<div class="grid">
    <div></div>
    <div></div>
</div>

Here is working example:

CSS

body {
  margin: 0px;
  padding: 0px;
  text-align: center;
}

.table {
  width: 100%;
  height: 100%;
  display: grid;
  grid-template-rows: 50px auto;
}
.table-heading {
  background-color: #ddd;
}
.table-content {
  overflow-x: hidden;
  overflow-y: scroll;
}

HTML

<html>
    <head>
    </head>
    <body>
        <div class="table">
            <div class="table-heading">
                HEADING
            </div>
            <div class="table-content">
                CONTENT - CONTENT - CONTENT <br/>
                CONTENT - CONTENT - CONTENT <br/>
                CONTENT - CONTENT - CONTENT <br/>
                CONTENT - CONTENT - CONTENT <br/>
                CONTENT - CONTENT - CONTENT <br/>
                CONTENT - CONTENT - CONTENT <br/>
            </div>
        </div>
    </body>
</html>

H
Henrik Haftmann

I have tried it using transformation:translate. While it works good in Firefox and Chrome, there is simply no function in IE11. No double scroll bars. Supports table tfoot and caption. Pure Javascript, no jQuery.

http://jsfiddle.net/wbLqzrfb/42/

thead.style.transform="translate(0,"+(dY-top-1)+"px)";

L
Lexsoul

I found a solution without jquery

HTML

<table class="fixed_header">
  <thead>
    <tr>
      <th>Col 1</th>
      <th>Col 2</th>
      <th>Col 3</th>
      <th>Col 4</th>
      <th>Col 5</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>row 1-0</td>
      <td>row 1-1</td>
      <td>row 1-2</td>
      <td>row 1-3</td>
      <td>row 1-4</td>
    </tr>
    <tr>
      <td>row 2-0</td>
      <td>row 2-1</td>
      <td>row 2-2</td>
      <td>row 2-3</td>
      <td>row 2-4</td>
    </tr>
    <tr>
      <td>row 3-0</td>
      <td>row 3-1</td>
      <td>row 3-2</td>
      <td>row 3-3</td>
      <td>row 3-4</td>
    </tr>
    <tr>
      <td>row 4-0</td>
      <td>row 4-1</td>
      <td>row 4-2</td>
      <td>row 4-3</td>
      <td>row 4-4</td>
    </tr>
    <tr>
      <td>row 5-0</td>
      <td>row 5-1</td>
      <td>row 5-2</td>
      <td>row 5-3</td>
      <td>row 5-4</td>
    </tr>
    <tr>
      <td>row 6-0</td>
      <td>row 6-1</td>
      <td>row 6-2</td>
      <td>row 6-3</td>
      <td>row 6-4</td>
    </tr>
    <tr>
      <td>row 7-0</td>
      <td>row 7-1</td>
      <td>row 7-2</td>
      <td>row 7-3</td>
      <td>row 7-4</td>
    </tr>
  </tbody>
</table>

CSS

.fixed_header{
    width: 400px;
    table-layout: fixed;
    border-collapse: collapse;
}

.fixed_header tbody{
  display:block;
  width: 100%;
  overflow: auto;
  height: 100px;
}

.fixed_header thead tr {
   display: block;
}

.fixed_header thead {
  background: black;
  color:#fff;
}

.fixed_header th, .fixed_header td {
  padding: 5px;
  text-align: left;
  width: 200px;
}

You can see it working here: https://jsfiddle.net/lexsoul/fqbsty3h

Source: https://medium.com/@vembarrajan/html-css-tricks-scroll-able-table-body-tbody-d23182ae0fbc


V
VigneshKarthik Kasiviswanathan

Most simple CSS, this will work single and multi rows of table headers too

 /* CSS */

.fix-head-table thead { 
    position: sticky;
    top: 0;
}

.fix-head-table thead th{ 
    box-shadow: inset 0px 0px 0px 0.5px rgb(192 192 192);
    border: none;
}

You can modify box-shadow inset "0.5px" shadow spread value in developer window css(inspect element) as your need and also color of that