[jquery] 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).

This question is related to jquery html html-table

The answer is


_x000D_
_x000D_
div.wrapper {_x000D_
    padding:20px;_x000D_
}_x000D_
table.scroll thead {_x000D_
    width: 100%;_x000D_
    background: #FC6822;_x000D_
}_x000D_
table.scroll thead tr:after {_x000D_
    content: '';_x000D_
    overflow-y: scroll;_x000D_
    visibility: hidden;_x000D_
}_x000D_
table.scroll thead th {_x000D_
    flex: 1 auto;_x000D_
    display: block;_x000D_
    color: #fff;_x000D_
}_x000D_
table.scroll tbody {_x000D_
    display: block;_x000D_
    width: 100%;_x000D_
    overflow-y: auto;_x000D_
    height: auto;_x000D_
    max-height: 200px;_x000D_
}_x000D_
table.scroll thead tr,_x000D_
table.scroll tbody tr {_x000D_
    display: flex;_x000D_
}_x000D_
table.scroll tbody tr td {_x000D_
    flex: 1 auto;_x000D_
    word-wrap: break;_x000D_
}_x000D_
table.scroll thead tr th,_x000D_
table.scroll tbody tr td {_x000D_
    width: 25%;_x000D_
    padding: 5px;_x000D_
    text-align-left;_x000D_
    border-bottom: 1px solid rgba(0,0,0,0.3);_x000D_
}
_x000D_
<div class="wrapper">_x000D_
    <table border="0" cellpadding="0" cellspacing="0" class="scroll">_x000D_
        <thead>_x000D_
            <tr>_x000D_
                <th>Name</th>_x000D_
                <th>Vorname</th>_x000D_
                <th>Beruf</th>_x000D_
                <th>Alter</th>_x000D_
            </tr>_x000D_
        </thead>_x000D_
        <tbody>_x000D_
            <tr>_x000D_
                <td>Müller</td>_x000D_
                <td>Marie</td>_x000D_
                <td>Künstlerin</td>_x000D_
                <td>26</td>_x000D_
            </tr>_x000D_
            <tr>_x000D_
                <td>Meier</td>_x000D_
                <td>Stefan</td>_x000D_
                <td>Chemiker</td>_x000D_
                <td>52</td>_x000D_
            </tr>_x000D_
            <tr>_x000D_
                <td>Schmidt</td>_x000D_
                <td>Sabrine</td>_x000D_
                <td>Studentin</td>_x000D_
                <td>38</td>_x000D_
            </tr>_x000D_
            <tr>_x000D_
                <td>Mustermann</td>_x000D_
                <td>Max</td>_x000D_
                <td>Lehrer</td>_x000D_
                <td>41</td>_x000D_
            </tr>_x000D_
            <tr>_x000D_
                <td>Müller</td>_x000D_
                <td>Marie</td>_x000D_
                <td>Künstlerin</td>_x000D_
                <td>26</td>_x000D_
            </tr>_x000D_
            <tr>_x000D_
                <td>Meier</td>_x000D_
                <td>Stefan</td>_x000D_
                <td>Chemiker</td>_x000D_
                <td>52</td>_x000D_
            </tr>_x000D_
            <tr>_x000D_
                <td>Schmidt</td>_x000D_
                <td>Sabrine</td>_x000D_
                <td>Studentin</td>_x000D_
                <td>38</td>_x000D_
            </tr>_x000D_
            <tr>_x000D_
                <td>Mustermann</td>_x000D_
                <td>Max</td>_x000D_
                <td>Lehrer</td>_x000D_
                <td>41</td>_x000D_
            </tr>_x000D_
            <tr>_x000D_
                <td>Müller</td>_x000D_
                <td>Marie</td>_x000D_
                <td>Künstlerin</td>_x000D_
                <td>26</td>_x000D_
            </tr>_x000D_
            <tr>_x000D_
                <td>Meier</td>_x000D_
                <td>Stefan</td>_x000D_
                <td>Chemiker</td>_x000D_
                <td>52</td>_x000D_
            </tr>_x000D_
            <tr>_x000D_
                <td>Schmidt</td>_x000D_
                <td>Sabrine</td>_x000D_
                <td>Studentin</td>_x000D_
                <td>38</td>_x000D_
            </tr>_x000D_
            <tr>_x000D_
                <td>Mustermann</td>_x000D_
                <td>Max</td>_x000D_
                <td>Lehrer</td>_x000D_
                <td>41</td>_x000D_
            </tr>_x000D_
        </tbody>_x000D_
    </table>_x000D_
</div>
_x000D_
_x000D_
_x000D_

Demo: css fixed table header demo


Fix your issue with this

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

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


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/


Well, after reviewing all available solutions I wrote plugin which can freeze any row (not only th) at the top of page or container. It's very simple and very fast. Feel free to use it. http://maslianok.github.io/stickyRows/


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);

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


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

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


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

_x000D_
_x000D_
// JAVASCRIPT_x000D_
_x000D_
_x000D_
_x000D_
/*_x000D_
 * getStyleObject Plugin for jQuery JavaScript Library_x000D_
 * From: http://upshots.org/?p=112_x000D_
 _x000D_
Basic usage:_x000D_
$.fn.copyCSS = function(source){_x000D_
  var styles = $(source).getStyleObject();_x000D_
  this.css(styles);_x000D_
}_x000D_
*/_x000D_
_x000D_
(function($){_x000D_
    $.fn.getStyleObject = function(){_x000D_
        var dom = this.get(0);_x000D_
        var style;_x000D_
        var returns = {};_x000D_
        if(window.getComputedStyle){_x000D_
            var camelize = function(a,b){_x000D_
                return b.toUpperCase();_x000D_
            };_x000D_
            style = window.getComputedStyle(dom, null);_x000D_
            for(var i = 0, l = style.length; i < l; i++){_x000D_
                var prop = style[i];_x000D_
                var camel = prop.replace(/\-([a-z])/g, camelize);_x000D_
                var val = style.getPropertyValue(prop);_x000D_
                returns[camel] = val;_x000D_
            };_x000D_
            return returns;_x000D_
        };_x000D_
        if(style = dom.currentStyle){_x000D_
            for(var prop in style){_x000D_
                returns[prop] = style[prop];_x000D_
            };_x000D_
            return returns;_x000D_
        };_x000D_
        return this.css();_x000D_
    }_x000D_
})(jQuery);_x000D_
_x000D_
_x000D_
_x000D_
   _x000D_
//Floating Header of long table  PiotrC_x000D_
(function ( $ ) {_x000D_
    var tableTop,tableBottom,ClnH;_x000D_
    $.fn.FixedHeader = function(){_x000D_
        tableTop=this.offset().top,_x000D_
        tableBottom=this.outerHeight()+tableTop;_x000D_
        //Add Fixed header_x000D_
        this.after('<table id="fixH"></table>');_x000D_
        //Clone Header_x000D_
        ClnH=$("#fixH").html(this.find("thead").clone());_x000D_
        //set style_x000D_
        ClnH.css({'position':'fixed', 'top':'0', 'zIndex':'60', 'display':'none',_x000D_
        'border-collapse': this.css('border-collapse'),_x000D_
  'border-spacing': this.css('border-spacing'),_x000D_
        'margin-left': this.css('margin-left'),_x000D_
        'width': this.css('width')            _x000D_
        });_x000D_
        //rewrite style cell of header_x000D_
        $.each(this.find("thead>tr>th"), function(ind,val){_x000D_
            $(ClnH.find('tr>th')[ind]).css($(val).getStyleObject());_x000D_
        });_x000D_
    return ClnH;}_x000D_
    _x000D_
    $.fn.moveScroll=function(){_x000D_
        var offset = $(window).scrollTop();_x000D_
        if (offset > tableTop && offset<tableBottom){_x000D_
            if(ClnH.is(":hidden"))ClnH.show();_x000D_
            $("#fixH").css('margin-left',"-"+$(window).scrollLeft()+"px");_x000D_
        }_x000D_
        else if (offset < tableTop || offset>tableBottom){_x000D_
            if(!ClnH.is(':hidden'))ClnH.hide();_x000D_
        }_x000D_
    };_x000D_
})( jQuery );_x000D_
_x000D_
_x000D_
_x000D_
_x000D_
_x000D_
var $myfixedHeader = $("#repTb").FixedHeader();_x000D_
$(window).scroll($myfixedHeader.moveScroll);
_x000D_
/* CSS - important only NOT transparent background */_x000D_
_x000D_
#repTB{border-collapse: separate;border-spacing: 0;}_x000D_
_x000D_
#repTb thead,#fixH thead{background: #e0e0e0 linear-gradient(#d8d8d8 0%, #e0e0e0 25%, #e0e0e0 75%, #d8d8d8 100%) repeat scroll 0 0;border:1px solid #CCCCCC;}_x000D_
_x000D_
#repTb td{border:1px solid black}
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>_x000D_
_x000D_
_x000D_
<h3>example</h3> _x000D_
<table id="repTb">_x000D_
<thead>_x000D_
<tr><th>Col1</th><th>Column2</th><th>Description</th></tr>_x000D_
</thead>_x000D_
<tr><td>info</td><td>info</td><td>info</td></tr>_x000D_
<tr><td>info</td><td>info</td><td>info</td></tr>_x000D_
<tr><td>info</td><td>info</td><td>info</td></tr>_x000D_
<tr><td>info</td><td>info</td><td>info</td></tr>_x000D_
<tr><td>info</td><td>info</td><td>info</td></tr>_x000D_
<tr><td>info</td><td>info</td><td>info</td></tr>_x000D_
<tr><td>info</td><td>info</td><td>info</td></tr>_x000D_
<tr><td>info</td><td>info</td><td>info</td></tr>_x000D_
<tr><td>info</td><td>info</td><td>info</td></tr>_x000D_
<tr><td>info</td><td>info</td><td>info</td></tr>_x000D_
<tr><td>info</td><td>info</td><td>info</td></tr>_x000D_
</table>
_x000D_
_x000D_
_x000D_


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)";

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

_x000D_
_x000D_
.table-fixed-header {_x000D_
  display: flex;_x000D_
  justify-content: space-between;_x000D_
  margin-right: 18px_x000D_
}_x000D_
_x000D_
.table-fixed {_x000D_
  display: flex;_x000D_
  justify-content: space-between;_x000D_
  height: 150px;_x000D_
  overflow: scroll;_x000D_
}_x000D_
_x000D_
.column {_x000D_
  flex-basis: 24%;_x000D_
  border-radius: 5px;_x000D_
  padding: 5px;_x000D_
  text-align: center;_x000D_
}_x000D_
.column .title {_x000D_
  border-bottom: 2px grey solid;_x000D_
  border-top: 2px grey solid;_x000D_
  text-align: center;_x000D_
  display: block;_x000D_
  font-weight: bold;_x000D_
}_x000D_
_x000D_
.cell {_x000D_
  padding: 5px;_x000D_
  border-right: 1px solid;_x000D_
  border-left: 1px solid;_x000D_
}_x000D_
_x000D_
.cell:nth-of-type(even) {_x000D_
  background-color: lightgrey;_x000D_
}
_x000D_
<!DOCTYPE html>_x000D_
<html>_x000D_
<head>_x000D_
  <meta charset="utf-8">_x000D_
  <meta name="viewport" content="width=device-width">_x000D_
  <title>Fixed header Bin</title>_x000D_
</head>_x000D_
<body>_x000D_
<div class="table-fixed-header">_x000D_
    _x000D_
    <div class="column">_x000D_
      <span class="title">col 1</span>_x000D_
    </div>_x000D_
    <div class="column">_x000D_
      <span class="title">col 2</span>_x000D_
    </div>_x000D_
    <div class="column">_x000D_
      <span class="title">col 3</span>_x000D_
    </div>_x000D_
    <div class="column">_x000D_
      <span class="title">col 4</span>_x000D_
    </div>_x000D_
    _x000D_
  </div>_x000D_
  _x000D_
  <div class="table-fixed">_x000D_
    _x000D_
    <div class="column">_x000D_
      <div class="cell">alpha</div>_x000D_
      <div class="cell">beta</div>_x000D_
      <div class="cell">ceta</div>_x000D_
    </div>_x000D_
    _x000D_
    <div class="column">_x000D_
      <div class="cell">alpha</div>_x000D_
      <div class="cell">beta</div>_x000D_
      <div class="cell">ceta</div>_x000D_
      <div class="cell">alpha</div>_x000D_
      <div class="cell">beta</div>_x000D_
      <div class="cell">ceta</div>_x000D_
      <div class="cell">alpha</div>_x000D_
      <div class="cell">beta</div>_x000D_
      <div class="cell">ceta</div>_x000D_
    </div>_x000D_
    _x000D_
    <div class="column">_x000D_
      <div class="cell">alpha</div>_x000D_
      <div class="cell">beta</div>_x000D_
      <div class="cell">ceta</div>_x000D_
      <div class="cell">beta</div>_x000D_
      <div class="cell">beta</div>_x000D_
      <div class="cell">beta</div>_x000D_
      _x000D_
    </div>_x000D_
    _x000D_
    <div class="column">_x000D_
      <div class="cell">alpha</div>_x000D_
      <div class="cell">beta</div>_x000D_
      <div class="cell">ceta</div>_x000D_
    </div>_x000D_
    _x000D_
  </div>_x000D_
</body>_x000D_
</html>
_x000D_
_x000D_
_x000D_


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);

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


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>

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: 10; // any positive value, layer order is global
    background: #fff;
}

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:

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

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.


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


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/


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>

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


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.


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.


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'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>

Examples related to jquery

How to make a variable accessible outside a function? Jquery assiging class to th in a table Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Getting all files in directory with ajax Bootstrap 4 multiselect dropdown Cross-Origin Read Blocking (CORB) bootstrap 4 file input doesn't show the file name Jquery AJAX: No 'Access-Control-Allow-Origin' header is present on the requested resource how to remove json object key and value.?

Examples related to html

Embed ruby within URL : Middleman Blog Please help me convert this script to a simple image slider Generating a list of pages (not posts) without the index file Why there is this "clear" class before footer? Is it possible to change the content HTML5 alert messages? Getting all files in directory with ajax DevTools failed to load SourceMap: Could not load content for chrome-extension How to set width of mat-table column in angular? How to open a link in new tab using angular? ERROR Error: Uncaught (in promise), Cannot match any routes. URL Segment

Examples related to html-table

Table column sizing DataTables: Cannot read property 'length' of undefined TypeError: a bytes-like object is required, not 'str' in python and CSV How to get the <td> in HTML tables to fit content, and let a specific <td> fill in the rest How to stick table header(thead) on top while scrolling down the table rows with fixed header(navbar) in bootstrap 3? Sorting table rows according to table header column using javascript or jquery How to make background of table cell transparent How to auto adjust table td width from the content bootstrap responsive table content wrapping How to print table using Javascript?