74

I have a problem with html/css sticky header + scrollbars. I am creating a program that requires scrollbars to show up once the containersize reaches a certain point(depending on resolution of the user).

I am forcing a min-width on the second column in the table, so the table stops decreasing at a certain point and forces the container to stay at a certain width. The overflow on the container shows the horizontal scrollbar. Everything works fine. Once i add a second scrollbar for the vertical scrolling, things are getting messed up. Does someone have a solution for this problem? I want to have a vertical scrollbar on the .table-body, but the scrollbar must be visible on the outercontainer.

Is there a good html/css solution for fixed header tables? I have been searching for a week, but can only find jQuery plugins for this kind of behaviour.

This is my current HTML:

<!DOCTYPE html>
<html>
<head>
    <title>fixed header prototyping</title>
    <link rel="stylesheet" href="css/style.css">
</head>

<body>
<div class="outer-container"> <!-- absolute positioned container -->
<div class="inner-container">

    <div class="table-header">
        <table id="headertable" width="100%" cellpadding="0" cellspacing="0">
            <thead>
                <tr>
                    <th class="header-cell col1">One</th>
                    <th class="header-cell col2">Two</th>
                    <th class="header-cell col3">Three</th>
                    <th class="header-cell col4">Four</th>
                    <th class="header-cell col5">Five</th>
                </tr>
            </thead>
        </table>
    </div>

    <div class="table-body">
        <table id="bodytable" width="100%" cellpadding="0" cellspacing="0">
            <tbody>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>
</div>



</body>
</html>

And my CSS looks like this:

body {
    margin:0;
    padding:0;
    height: 100%;
    width: 100%;
}
table {
    border-collapse: collapse; /* make simple 1px lines borders if border defined */
}
tr {
    width: 100%;
}

.outer-container {
    background-color: #ccc;
    position: absolute;
    top:0;
    left: 0;
    right: 300px;
    bottom:40px;
    overflow: hidden;

}
.inner-container {
    width: 100%;
    height: 100%;
    position: relative;
    overflow-x: scroll;
    overflow-y:hidden;
}
.table-header {
    float:left;
    width: 100%;
}
.table-body {
    float:left;
    height: 100%;
    width: inherit;
    overflow-y: scroll;
}
.header-cell {
    background-color: yellow;
    text-align: left;
    height: 40px;
}
.body-cell {
    background-color: blue;
    text-align: left;
}
.col1, .col3, .col4, .col5 {
    width:120px;
    min-width: 120px;
}
.col2 {
    min-width: 300px;
}

JSFiddle example - http://jsfiddle.net/W8URM/

starball
  • 20,030
  • 7
  • 43
  • 238
Remco Bakker
  • 741
  • 1
  • 6
  • 3

10 Answers10

74

This is not an easy one. I've come up with a Script solution. (I don't think this can be done using pure CSS)

the HTML stays the same as you posted, the CSS changes a little bit, JQuery code added.

Working Fiddle Tested on: IE10, IE9, IE8, FF, Chrome

BTW: if you have unique elements, why don't you use id's instead of classes? I think it gives a better selector performance.

Explanation of how it works: inner-container will span the entire space of the outer-container (so basically, he's not needed) but I left him there, so you wont need to change you DOM.

the table-header is relatively positioned, without a scroll (overflow: hidden), we will handle his scroll later.

the table-body have to span the rest of the inner-container height, so I used a script to determine what height to fix him. (it changes dynamically when you re-size the window) without a fixed height, the scroll wont appear, because the div will just grow large instead.. notice that this part can be done without script, if you fix the header height and use CSS3 (as shown in the end of the answer)

now it's just a matter of moving the header along with the body each time we scroll. this is done by a function assigned to the scroll event.

CSS (some of it was copied from your style)

*
{
    padding: 0;
    margin: 0;
}

body
{
    height: 100%;
    width: 100%;
}
table
{
    border-collapse: collapse; /* make simple 1px lines borders if border defined */
}
.outer-container
{
    background-color: #ccc;
    position: absolute;
    top:0;
    left: 0;
    right: 300px;
    bottom: 40px;
}

.inner-container
{
    height: 100%;
    overflow: hidden;
}

.table-header
{
    position: relative;
}
.table-body
{
    overflow: auto;
}

.header-cell
{
    background-color: yellow;
    text-align: left;
    height: 40px;
}
.body-cell 
{
    background-color: blue;
    text-align: left;
}
.col1, .col3, .col4, .col5
{
    width:120px;
    min-width: 120px;
}
.col2
{
    min-width: 300px;
}

JQuery

$(document).ready(function () {
    setTableBody();
    $(window).resize(setTableBody);
    $(".table-body").scroll(function ()
    {
        $(".table-header").offset({ left: -1*this.scrollLeft });
    });
});

function setTableBody()
{
    $(".table-body").height($(".inner-container").height() - $(".table-header").height());
}

If you don't care about fixing the header height (I saw that you fixed the cell's height in your CSS), some of the Script can be skiped if you use CSS3 :Shorter Fiddle (this will not work on IE8)

avrahamcool
  • 13,888
  • 5
  • 47
  • 58
  • 3
    Upvoted! I share your opinion that this cannot be achieved by pure CSS. IMHO your solution is exactly what was asked for. The original markup already isn't very semantically, because splitting a table's `thead` and `tbody`, plus wrapping each one in a DIV doesn't look "correct" to me. – Netsurfer Sep 17 '13 at 14:42
  • I have similar implement in my page as well, there is one issue that bothers me though: How do you handle the page in rtl direction, scrollLeft is not applicable anymore? – 9blue Sep 09 '14 at 19:49
  • @Zen without putting to much thinking into it, I'd say that you can replace `left: -1*this.scrollLeft` by `right: -1*this.scrollRight`. post a relevant fiddle, and we'll continue from there. – avrahamcool Sep 10 '14 at 07:05
  • @avrahamcool scrollRight is not a value I can get from native javascript fiddle is here [link](http://jsfiddle.net/eZXtq/246/) based on your fiddle – 9blue Sep 10 '14 at 15:16
  • @avrahamcool Why your `Tested on: IE10, IE9, IE8, FF, Chrome` fiddle solution not working on `ie8` while using `jquery-1.7.2` can you please tell me – Vivek Vikranth Oct 30 '14 at 13:11
  • @vikranth For IE I've tested it on my local machine. – avrahamcool Oct 30 '14 at 16:56
  • This solution forces a fixed column width. That takes away one of the table elements most useful features. – Billy McKee Apr 16 '15 at 12:56
  • Nice solution. This should however be changed to CSS3 transform to use hardware acceleration instead of repainting with every scroll. http://jsfiddle.net/eZXtq/665/ – Chris Andersson Jan 21 '16 at 15:18
  • I made simplified version without inner & outer containers for my own needs. If anyone else needs it: http://jsfiddle.net/7Lgoaos7/ – kikovi Dec 28 '16 at 15:38
  • 1
    @kikovi your demo doesn't work well. the title hides the first content row, and we can see through the heading when we scroll, and the bottom scroll is visible only when we scroll to the bottom. – avrahamcool Dec 28 '16 at 16:41
  • 1
    @avrahamcool you are correct. I didn't polish the demo completely, because it already suited my needs. Here is the polished version, if anyone needs it: http://jsfiddle.net/kq1gzzmm/. Bottom scroll depends of the container height and the title is set to position relative, so it doesn't hide first content row. – kikovi Dec 29 '16 at 14:15
  • @avrahamcool wondering if you can help out with my version of the question : https://stackoverflow.com/questions/47073223/vue-js-2-table-title-scroll-horizontally-with-content-but-not-vertically – tatsu Nov 02 '17 at 15:19
  • I needed a version that works with input elements in the header too (and tab-scrolling) so I hacked the original fiddle into one that has a hidden scrollbar for the headers: http://jsfiddle.net/bmj4wkcL/ It's quick and dirty, feel free to improve if needed, – Martien de Jong Mar 19 '20 at 09:02
57

Here's a solution which again is not a CSS only solution. It is similar to avrahamcool's solution in that it uses a few lines of jQuery, but instead of changing heights and moving the header along, all it does is changing the width of tbody based on how far its parent table is scrolled along to the right.

An added bonus with this solution is that it works with a semantically valid HTML table.

It works great on all recent browser versions (IE10, Chrome, FF) and that's it, the scrolling functionality breaks on older versions.

But then the fact that you are using a semantically valid HTML table will save the day and ensure the table is still displayed properly, it's only the scrolling functionality that won't work on older browsers.

Here's a jsFiddle for demonstration purposes.

CSS

table {
    width: 300px;
    overflow-x: scroll;
    display: block;
}
thead, tbody {
    display: block;
}
tbody {
    overflow-y: scroll;
    overflow-x: hidden;
    height: 140px;
}
td, th {
    min-width: 100px;
}

JS

$("table").on("scroll", function () {
    $("table > *").width($("table").width() + $("table").scrollLeft());
});

I needed a version which degrades nicely in IE9 (no scrolling, just a normal table). Posting the fiddle here as it is an improved version. All you need to do is set a height on the tr.

Additional CSS to make this solution degrade nicely in IE9

tr {
    height: 25px; /* This could be any value, it just needs to be set. */ 
}

Here's a jsFiddle demonstrating the nicely degrading in IE9 version of this solution.

Edit: Updated fiddle links to link to a version of the fiddle which contains fixes for issues mentioned in the comments. Just adding a snippet with the latest and greatest version while I'm at it:

$('table').on('scroll', function() {
  $("table > *").width($("table").width() + $("table").scrollLeft());
});
html {
  font-family: verdana;
  font-size: 10pt;
  line-height: 25px;
}

table {
  border-collapse: collapse;
  width: 300px;
  overflow-x: scroll;
  display: block;
}

thead {
  background-color: #EFEFEF;
}

thead,
tbody {
  display: block;
}

tbody {
  overflow-y: scroll;
  overflow-x: hidden;
  height: 140px;
}

td,
th {
  min-width: 100px;
  height: 25px;
  border: dashed 1px lightblue;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
  <thead>
    <tr>
      <th>Column 1</th>
      <th>Column 2</th>
      <th>Column 3</th>
      <th>Column 4</th>
      <th>Column 5</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>AAAAAAAAAAAAAAAAAAAAAAAAAA</td>
      <td>Row 1</td>
      <td>Row 1</td>
      <td>Row 1</td>
      <td>Row 1</td>
    </tr>
    <tr>
      <td>Row 2</td>
      <td>Row 2</td>
      <td>Row 2</td>
      <td>Row 2</td>
      <td>Row 2</td>
    </tr>
    <tr>
      <td>Row 3</td>
      <td>Row 3</td>
      <td>Row 3</td>
      <td>Row 3</td>
      <td>Row 3</td>
    </tr>
    <tr>
      <td>Row 4</td>
      <td>Row 4</td>
      <td>Row 4</td>
      <td>Row 4</td>
      <td>Row 4</td>
    </tr>
    <tr>
      <td>Row 5</td>
      <td>Row 5</td>
      <td>Row 5</td>
      <td>Row 5</td>
      <td>Row 5</td>
    </tr>
    <tr>
      <td>Row 6</td>
      <td>Row 6</td>
      <td>Row 6</td>
      <td>Row 6</td>
      <td>Row 6</td>
    </tr>
    <tr>
      <td>Row 7</td>
      <td>Row 7</td>
      <td>Row 7</td>
      <td>Row 7</td>
      <td>Row 7</td>
    </tr>
    <tr>
      <td>Row 8</td>
      <td>Row 8</td>
      <td>Row 8</td>
      <td>Row 8</td>
      <td>Row 8</td>
    </tr>
    <tr>
      <td>Row 9</td>
      <td>Row 9</td>
      <td>Row 9</td>
      <td>Row 9</td>
      <td>Row 9</td>
    </tr>
    <tr>
      <td>Row 10</td>
      <td>Row 10</td>
      <td>Row 10</td>
      <td>Row 10</td>
      <td>Row 10</td>
    </tr>
  </tbody>
</table>
Mathijs Flietstra
  • 12,900
  • 3
  • 38
  • 67
  • 2
    Very simple and very good solution :), but if you need to use two or more tables on the same page you can implement the following code: `$(window).load(function(){ $('table').on('scroll', function () { $(this).find(" > *").width($(this).width() + $(this).scrollLeft()); }); });` Thanks a lot Mathijs for the idea. – Jerzy Gebler May 10 '14 at 03:45
  • 1
    I want so badly for this to work perfectly, unfortunately if you click the scroll wheel to do mouse move scrolling in IE 10 & 11 it does the horizontal scrolling on the body and not the head, and doesn't even update the scroll bar. Any ideas on how to fix that one? – aron.duby Jun 26 '14 at 15:41
  • 1
    Same solution but without extraneous CSS (pet peave when demonstrating a CSS solution) https://jsfiddle.net/oozL98dx/ – AndyClaw Mar 13 '15 at 19:49
  • 3
    This works pretty well until your content is > 100px wide. Then the TH width does not match the corresponding TD width. – Billy McKee Apr 16 '15 at 12:59
  • @BillyMcKee - You should check [the original fiddle](http://jsfiddle.net/mathijsflietstra/X2Kmd/1/) in the answer, the problem you mention only shows up in the version without the "extraneous" CSS. It seems a little bit too much CSS was removed in that version.. – Mathijs Flietstra Apr 16 '15 at 14:21
  • @MathijsFlietstra [That fiddle still doesn't work](http://jsfiddle.net/X2Kmd/388/) if the content is > 100px wide (with no line break). – Billy McKee Apr 16 '15 at 20:57
  • @BillyMcKee - Ah, I see, that [could be fixed in this way](http://jsfiddle.net/X2Kmd/389/). I realise that this fixes the columns widths, but since I never set out to come up with a solution which supports flexible column widths based on their contents, I don't think that's an issue. I'm kind of kicking myself for not including this in the first place, so thanks for pointing it out :) – Mathijs Flietstra Apr 17 '15 at 06:58
  • @MathijsFlietstra wondering if you can help out with my version of the question : https://stackoverflow.com/questions/47073223/vue-js-2-table-title-scroll-horizontally-with-content-but-not-vertically would you know what I'm missing? – tatsu Nov 02 '17 at 15:19
  • can somebody help out with my version of question:https://stackoverflow.com/questions/51578325/responsive-design-for-html-wide-table – user466130 Jul 29 '18 at 15:45
  • There's still a problem: When content of cells are inputs, the scroll doesn't move when you change the focused imput with tabs. This one: https://jsfiddle.net/gtey5cz2/ (all versions fail). @anshuman 's response worked; https://jsfiddle.net/nstb69pz/ – Alfonso Tienda Aug 08 '19 at 18:35
  • if add direction:rtl to table this solution not work. Do you have any solution for that? – Ebrahim Abdollahian May 15 '20 at 08:41
  • 1
    @ebigood - response is a bit late :) but in order to make that work you'd have to swap the math around a bit as well: http://jsfiddle.net/fp9jra43 – Mathijs Flietstra Jun 10 '20 at 11:28
20

Here is a HTML / CSS only solution (with a little javascript).

Apology to answer the question after this long, but the solution given did not suit me and I found a better one. Here is the easiest way to do it with HTML (no jquery):

Before that, the solution fiddle to the question. https://jsfiddle.net/3vzrunkt/

<div>
    <div style="overflow:hidden;;margin-right:16px" id="headerdiv">
        <table id="headertable" style="min-width:900px" border=1>
            <thead>
                <tr>
                    <th style="width:120px;min-width:120px;">One</th>
                    <th style="width:420px;min-width:420px;">Two</th>
                    <th style="width:120px;min-width:120px;">Three</th>
                    <th style="width:120px;min-width:120px;">Four</th>
                    <th style="width:120px;min-width:120px;">Five</th>
                </tr>
            </thead>
        </table>
    </div>

    <div style="overflow-y:scroll;max-height:200px;" 
         onscroll="document.getElementById('headerdiv').scrollLeft = this.scrollLeft;">
        <table id="bodytable" border=1 style="min-width:900px; border:1px solid">
            <tbody>
                <tr>
                    <td style="width:120px;min-width:120px;">body row1</td>
                    <td style="width:420px;min-width:420px;">body row2</td>
                    <td style="width:120px;min-width:120px;">body row2</td>
                    <td style="width:120px;min-width:120px;">body row2</td>
                    <td style="width:120px;min-width:120px;">body row2 en nog meer</td>
                </tr>
                :
                :
                :
                :

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

And to explain the solution:

  1. you need and enclosing div no overflow/scroll required

  2. a header div containing the header table with overflow:hidden to ensure that the scrollbar is not displayed. Add margin-right:16px to ensure that the scrollbar is left outside it while synching.

  3. another div for containing the table records and overflow-y:scroll. Note the padding is required to get the scrollbar move right of the header.

  4. And the most important thing the magical js to sync the header and table data:

     onscroll="document.getElementById('headerdiv').scrollLeft = this.scrollLeft;"
    
Anshuman
  • 831
  • 5
  • 18
  • Good idea, although I would like an alternative only using a table as such – Neyer Dec 16 '16 at 13:25
  • Is there any possibility of extending your solution (without jQuery) also to fix column (last or first)? I've tried to set position: absolute but then it's unscrollable.. – adolzi May 24 '17 at 18:10
  • Cheers mate as css only solution this works like a charm. – Swoox Oct 31 '17 at 11:22
  • @Anshuman wondering if you can help out with my version of the question : https://stackoverflow.com/questions/47073223/vue-js-2-table-title-scroll-horizontally-with-content-but-not-vertically ? thanks. – tatsu Nov 02 '17 at 15:18
  • I tried this but my header is not scrolling horizontally. Any idea why? Please help. – S.. Apr 20 '18 at 13:07
  • @N. B. - can you create a fiddle and share so that I can check what's wrong. – Anshuman Apr 26 '18 at 09:56
  • Hello @Anshuman, I was actually integrating it in a system using a framework with predefined CSS. That's why I guess it was not working. Lots of overriding was happening. I no longer need it anyway. Thank you for replying. – S.. Apr 27 '18 at 07:03
  • 2
    This isn't an HTML/CSS-only solution, you have an `onscroll` which is JavaScript. – MatthewG Jul 26 '18 at 12:30
  • @MatthewG - agreed and edit has been done. Thanks for pointing it out – Anshuman Jul 30 '18 at 21:56
  • The header table and the body tables widths are not the same. It is noticeable when looking at the "yellow" header on the Fiddle; the yellow headers width is not the same as the "blue" table. I my case the situation is worse since I used a "fixed" position div, and then a 100% width of the non fixed position is not the same as the fixed positions 100%. To fix this one can use javascript and then set the widths to align them. e.g. onload="document.getElementById('thebody').style.width = document.getElementById('headerdiv').style.width; – Wayne Aug 08 '19 at 01:52
  • @Anshuman pardon me, your edit is still misleading. You saying you have "an HTML/CSS only solution (with a little JS)" is a contradictory statement - it can't be HTML/CSS only if it contains ANY other tech. It indeed contains a line of JS, so it is not HTML/CSS only. – Sensei James May 30 '20 at 17:33
4

This has been driving me crazy for literally weeks. I found a solution that will work for me that includes:

  1. Non-fixed column widths - they shrink and grow on window resizing.
  2. Table has a horizontal scroll-bar when needed, but not when it isn't.
  3. Number of columns is irrelevant - it will size to however many columns you need it to.
  4. Not all columns need to be the same width.
  5. Header goes all the way to the end of the right scrollbar.
  6. No javascript!

...but there are a couple of caveats:

  1. The vertical scrollbar is not visible until you scroll all the way to the right. Given that most people have scroll wheels, this was an acceptable sacrifice.

  2. The width of the scrollbar must be known. On my website I set the scrollbar widths (I'm not overly concerned with older, incompatible browsers), so I can then calculate div and table widths that adjust based on the scrollbar.

Instead of posting my code here, I'll post a link to the jsFiddle.

Fixed header table + scroll left/right.

elPastor
  • 8,435
  • 11
  • 53
  • 81
3

working example in jsFiddle

This can be achieved using div. It can be done with table too. But i always prefer div.

<body id="doc-body" style="width: 100%; height: 100%; overflow: hidden; position: fixed" onload="InitApp()"> 
    <div>
        <!--If you don't need header background color you don't need this div.-->
        <div id="div-header-hack" style="height: 20px; position: absolute; background-color: gray"></div>

        <div id="div-header" style="position: absolute; top: 0px; overflow: hidden; height: 20px; background-color: gray">                
        </div>

        <div id="div-item" style="position: absolute; top: 20px; overflow: auto" onscroll="ScrollHeader()">                
        </div>
    </div>
</body>

Javascript:
please refer jsFiddle for this part. Else this answer becomes very lengthy.

user10
  • 5,186
  • 8
  • 43
  • 64
2

The solution is to use JS to horizontally scroll the top div so that it matches the bottom div.

You must be very careful to make sure the top and bottom are exactly the same sizes, for example you might need to make the TD and TH use fixed widths.

Here is a fiddle https://jsfiddle.net/jdhenckel/yzjhk08h/5/

The important parts: CSS use

.head {
  overflow-x: hidden;
  overflow-y: scroll;
  width: 500px;
}
.lower {
  overflow-x: auto;
  overflow-y: scroll;
  width: 500px;
  height: 400px;
}

Notice overflow-y must be the same on both head and lower.

And the Javascript...

var head = document.querySelector('.head');

var lower = document.querySelector('.lower');

lower.addEventListener('scroll', function (e) {
  console.log(lower.scrollLeft);
  head.scrollLeft = lower.scrollLeft;
});
John Henckel
  • 10,274
  • 3
  • 79
  • 79
1

you can use following CSS code..

body {
    margin:0;
    padding:0;
    height: 100%;
    width: 100%;
}
table {
    border-collapse: collapse; /* make simple 1px lines borders if border defined */
}
tr {
    width: 100%;
}

.outer-container {
    background-color: #ccc;    
    top:0;
    left: 0;
    right: 300px;
    bottom:40px;
    overflow:hidden;

}
.inner-container {
    width: 100%;
    height: 100%;
    position: relative;

}
.table-header {
    float:left;
    width: 100%;
}
.table-body {
    float:left;
    height: 100%;
    width: inherit;

}
.header-cell {
    background-color: yellow;
    text-align: left;
    height: 40px;
}
.body-cell {
    background-color: blue;
    text-align: left;
}
.col1, .col3, .col4, .col5 {
    width:120px;
    min-width: 120px;
}
.col2 {
    min-width: 300px;
}
BenMorel
  • 34,448
  • 50
  • 182
  • 322
Arun
  • 257
  • 1
  • 5
  • 22
0

There's a jquery plugin for this: jquery.floatThead

phaworth
  • 399
  • 2
  • 8
0

As we all know maintaining your own solution is not the best. I recommend trying vh-sticky-table-header. This module enables sticky header and it works in horizontal scrolling of the table as well.

Also it is framework agnostic and has no dependencies, better suited for modern SPA frontend frameworks.

-2

If this is what you want only HTML and CSS solution

fiddle

Here's the HTML

<div class="outer-container"> <!-- absolute positioned container -->
<div class="inner-container">

    <div class="table-header">
        <table id="headertable" width="100%" cellpadding="0" cellspacing="0">
            <thead>
                <tr>
                    <th class="header-cell col1">One</th>
                    <th class="header-cell col2">Two</th>
                    <th class="header-cell col3">Three</th>
                    <th class="header-cell col4">Four</th>
                    <th class="header-cell col5">Five</th>
                </tr>
            </thead>
        </table>
    </div>

    <div class="table-body">
        <table id="bodytable" width="100%" cellpadding="0" cellspacing="0">
            <tbody>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
                <tr>
                    <td class="body-cell col1">body row1</td>
                    <td class="body-cell col2">body row2</td>
                    <td class="body-cell col3">body row2</td>
                    <td class="body-cell col4">body row2</td>
                    <td class="body-cell col5">body row2 en nog meer</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>
</div>

And this is the css

body {
margin:0;
padding:0;
height: 100%;
width: 100%;
}
table {
    border-collapse: collapse; /* make simple 1px lines borders if border defined */
}
tr {
    width: 100%;
}

.outer-container {
    background-color: #ccc;
    position: absolute;
    top:0;
    left: 0;
    right: 300px;
    bottom:40px;
    overflow: scroll;

}
.inner-container {
    width: 100%;
    height: 100%;
    position: relative;
    overflow-x: visible;
    overflow-y:visible;
}
.table-header {
    float:left;
    width: 100%;
}
.table-body {
    float:left;
    height: auto;
    width: auto;
    overflow: visible;
    background-color: red;
}
.header-cell {
    background-color: yellow;
    text-align: left;
    height: 40px;
}
.body-cell {
    background-color: transparent;
    text-align: left;
}
.col1, .col3, .col4, .col5 {
    width:120px;
    min-width: 120px;
}
.col2 {
    min-width: 300px;
}

Let me know if this is what you need. Or some thing is missing. I went through the other answers and found that jquery has been used . I took it on assumption that you need css solution . If I am missing any more point please mention :)

Anobik
  • 4,841
  • 2
  • 17
  • 32