ChatGPT解决这个技术问题 Extra ChatGPT

HTML table headers always visible at top of window when viewing a large table

I would like to be able to "tweak" an HTML table's presentation to add a single feature: when scrolling down through the page so that the table is on the screen but the header rows are off-screen, I would like the headers to remain visible at the top of the viewing area.

This would be conceptually like the "freeze panes" feature in Excel. However, an HTML page might contain several tables in it and I only would want it to happen for the table that is currently in-view, only while it is in-view.

Note: I've seen one solution where the table data area is made scrollable while the headers do not scroll. That's not the solution I'm looking for.

is your page designed using tables? is it not possible to convert it to divs first and leave tabular data alone for the tables?
i recently wrote a plugin that does this: programmingdrunk.com/floatThead
In addition to the top-voted solution and the derivatives of it below, @mkoryak 's plugin above is also quite good. Be sure to take a look at that too before you finish "shopping".
Thanks dan :) I have some kick-ass features planned for the next release too
I second the @mkoryak floatThead plugin - I was able to swiftly integrate it. Big thanks for the plugin!

H
Hendy Irawan

Check out jQuery.floatThead (demos available) which is very cool, can work with DataTables too, and can even work inside an overflow: auto container.


yes it works with horizontal scrolling, window scrolling, inner scrolling and window resizing.
Could you add a fiddle with a working code? (I know that there is in the site...)
i've built a huge demo / docs page. Why do you need a fiddle?
@MustModify stackoverflow is not a good place to ask me questions. report an issue on github, even if its just a question about how to get it working. Ill respond there usually in under a day. Here ill probably respond in just under a year :)
@MustModify for what its worth, none of the classes on the tables in the examples are relevant. The plugin does not need any css or classes to work, as my docs say.
j
jmosbech

Craig, I refined your code a bit (among a few other things it's now using position:fixed) and wrapped it as a jQuery plugin.

Try it out here: http://jsfiddle.net/jmosbech/stFcx/

And get the source here: https://github.com/jmosbech/StickyTableHeaders


Good idea to make a jQuery plugin. A couple of questions: (1) Does it work well with horizontal scrolling? (2) How does it behave when you scroll down so the bottom of the table is scrolling off the top of the window? The example doesn't allow testing of those cases.
Your jsfiddle example works great with tablesorter, but the code on github does not. Did you have to modify the tablesorter code?
Craig, horizontal scrolling shouldn't be a problem since the header is re-positioned both horizontally and vertically on scroll and resize. As for your second question: The header is hidden when the table leaves the viewport.
Eric, that's strange. Did you try to open /demo/tablesorter.htm from Github? I haven't modified the tablesorter code, but in order to make it work you need to apply the StickyTableHeader plugin before tablesorter.
The horizonal scrolling doesn't quite work, after scrolling quite a distance the thead is positioned further left when scrolled off-screen than when it is positioned normally as part of the table.
w
willy wonka

If you're targeting modern css3 compliant browsers (Browser support: https://caniuse.com/#feat=css-sticky) you can use position:sticky, which doesn't require JS and won't break the table layout miss-aligning th and td of the same column. Nor does it require fixed column width to work properly.

Example for a single header row:

thead th
{
    position: sticky;
    top: 0px;
}

For theads with 1 or 2 rows, you can use something like this:

thead > :last-child th
{
    position: sticky;
    top: 30px; /* This is for all the the "th" elements in the second row, (in this casa is the last child element into the thead) */
}

thead > :first-child th
{
    position: sticky;
    top: 0px; /* This is for all the the "th" elements in the first child row */
}

You might need to play a bit with the top property of the last child changing the number of pixels to match the height of the first row (+ the margin + the border + the padding, if any), so the second row sticks just down bellow the first one.

Also both solutions work even if you have more than one table in the same page: the th element of each one starts to be sticky when its top position is the one indicated into the css definition and just disappear when all the table scrolls down. So if there are more tables all work beautifully the same way.

Why to use last-child before and first-child after in the css?

Because css rules are rendered by the browser in the same order as you write them into the css file and because of this if you have just 1 row into the thead element the first row is simultaneously the last row too and the first-child rule need to override the last-child one. If not you will have an offset of the row 30 px from the top margin which I suppose you don't want to.

A known problem of position: sticky is that it doesn't work on thead elements or table rows: you must target th elements. Hopping this issue will be solved on future browser versions.


First example does not appear to work in Firefox (61). However, if position sticky is set on the thead, it does.
You could do thead tr+tr th to target the second row.
D
David Castro

The most simple answer only using CSS :D !!!

table { /* Not required only for visualizing */ border-collapse: collapse; width: 100%; } table thead tr th { /* you could also change td instead th depending your html code */ background-color: green; position: sticky; z-index: 100; top: 0; } td { /* Not required only for visualizing */ padding: 1em; }

Col1 Col2 Col3
data data data
data data data
data data data
data data data
data data data
data data data
data data data
data data data
data data data
data data data
data data data
data data data
data data data
data data data
david castro rocks!


Thanks David, it is the nicest approach i have found in this question
C
Craig McQueen

Possible alternatives

js-floating-table-headers

js-floating-table-headers (Google Code)

In Drupal

I have a Drupal 6 site. I was on the admin "modules" page, and noticed the tables had this exact feature!

Looking at the code, it seems to be implemented by a file called tableheader.js. It applies the feature on all tables with the class sticky-enabled.

For a Drupal site, I'd like to be able to make use of that tableheader.js module as-is for user content. tableheader.js doesn't seem to be present on user content pages in Drupal. I posted a forum message to ask how to modify the Drupal theme so it's available. According to a response, tableheader.js can be added to a Drupal theme using drupal_add_js() in the theme's template.php as follows:

drupal_add_js('misc/tableheader.js', 'core');

Your Google Code link was very useful for me, tahnks.
S
Sergej

I've encountered this problem very recently. Unfortunately, I had to do 2 tables, one for the header and one for the body. It's probably not the best approach ever but here goes:

oh hai

col header col header
data1 data1
data2 data2

This worked for me, it's probably not the elegant way but it does work. I'll investigate so see if I can do something better, but it allows for multiple tables.

Go read on the overflow propriety to see if it fits your need


Hmm not working, i'll try to fix this, but the approach remains, you have to use the overflow:auto proprety
I appreciate your solution and its simplicity, however in my question I said "Note: I've seen one solution where the table data area is made scrollable while the headers do not scroll. That's not the solution I'm looking for."
@GabRoyer The w3school page, you referenced, does not exist.
They seem to have moved it. Fixed
Another problem with this approach is that if you need to adapt some of the columns to other content you risk to break their alignment with the corresponding column header. IMHO it is better to face this problem with an approach that allows a kinda of dynamic management of the content that keeps a perfect alignment of columns and relative heads. So IMHO position=sticky is the way to go.
s
staticsan

Using display: fixed on the thead section should work, but for it only work on the current table in view, you will need the help of JavaScript. And it will be tricky because it will need to figure out scrolling places and location of elements relative to the viewport, which is one of the prime areas of browser incompatibility.

Have a look at the popular JavaScript frameworks (jQuery, MooTools, YUI, etc etc.) to see if they can either do what you want or make it easier to do what you want.


Thanks for the advice. I did make a solution using jQuery—see my answer.
I tried this and am thinking you meant position rather than display, but neither worked for thead in chome.
@ericslaw Ack - yes I meant position: fixed. Sorry it didn't work in Chrome.
messed my header by ignoring width styles
m
merkuro

If you use a full screen table you are maybe interested in setting th to display:fixed; and top:0; or try a very similar approach via css.

Update

Just quickly build up a working solution with iframes (html4.0). This example IS NOT standard conform, however you will easily be able to fix it:

outer.html

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">   
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">     
    <head>      
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />   
        <title>Outer</title>
  <body>
    <iframe src="test.html" width="200" height="100"></iframe>
    </body>
</html> 

test.html

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">   
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">     
    <head>      
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />   
        <title>Floating</title>
    <style type="text/css">
      .content{
        position:relative; 
      }

      thead{
        background-color:red;
        position:fixed; 
        top:0;
      }
    </style>
  <body>
    <div class="content">      
      <table>
        <thead>
          <tr class="top"><td>Title</td></tr>
        </head>
        <tbody>
          <tr><td>a</td></tr>
          <tr><td>b</td></tr>
          <tr><td>c</td></tr>
          <tr><td>d</td></tr>
          <tr><td>e</td></tr>
          <tr><td>e</td></tr>
          <tr><td>e</td></tr>
          <tr><td>e</td></tr>
          <tr><td>e</td></tr>
          <tr><td>e</td></tr>
        </tbody>
      </table>
    </div>
    </body>
</html> 

@a_m0d, true...but the request sounds a big confusing. Either we are missunderstanding it, or the asker doesn't completely understand what he wants.
Here is another solution using some javascript imaputz.com/cssStuff/bigFourVersion.html
m
marc_s

This is really a tricky thing to have a sticky header on your table. I had same requirement but with asp:GridView and then I found it really thought to have sticky header on gridview. There are many solutions available and it took me 3 days trying all the solution but none of them could satisfy.

The main issue that I faced with most of these solutions was the alignment problem. When you try to make the header floating, somehow the alignment of header cells and body cells get off track.

With some solutions, I also got issue of getting header overlapped to first few rows of body, which cause body rows getting hidden behind the floating header.

So now I had to implement my own logic to achieve this, though I also not consider this as perfect solution but this could also be helpful for someone,

Below is the sample table.

<div class="table-holder">
        <table id="MyTable" cellpadding="4" cellspacing="0" border="1px" class="customerTable">
            <thead>
                <tr><th>ID</th><th>First Name</th><th>Last Name</th><th>DOB</th><th>Place</th></tr>
            </thead>
            <tbody>
                <tr><td>1</td><td>Customer1</td><td>LastName</td><td>1-1-1</td><td>SUN</td></tr>
                <tr><td>2</td><td>Customer2</td><td>LastName</td><td>2-2-2</td><td>Earth</td></tr>
                <tr><td>3</td><td>Customer3</td><td>LastName</td><td>3-3-3</td><td>Mars</td></tr>
                <tr><td>4</td><td>Customer4</td><td>LastName</td><td>4-4-4</td><td>Venus</td></tr>
                <tr><td>5</td><td>Customer5</td><td>LastName</td><td>5-5-5</td><td>Saturn</td></tr>
                <tr><td>6</td><td>Customer6</td><td>LastName</td><td>6-6-6</td><td>Jupitor</td></tr>
                <tr><td>7</td><td>Customer7</td><td>LastName</td><td>7-7-7</td><td>Mercury</td></tr>
                <tr><td>8</td><td>Customer8</td><td>LastName</td><td>8-8-8</td><td>Moon</td></tr>
                <tr><td>9</td><td>Customer9</td><td>LastName</td><td>9-9-9</td><td>Uranus</td></tr>
                <tr><td>10</td><td>Customer10</td><td>LastName</td><td>10-10-10</td><td>Neptune</td></tr>
            </tbody>
        </table>
    </div>

Note: The table is wrapped into a DIV with class attribute equal to 'table-holder'.

Below is the JQuery script that I added in my html page header.

<script src="../Scripts/jquery-1.7.2.min.js" type="text/javascript"></script>
<script src="../Scripts/jquery-ui.min.js" type="text/javascript"></script>
<script type="text/javascript">
    $(document).ready(function () {
        //create var for table holder
        var originalTableHolder = $(".table-holder");
        // set the table holder's with
        originalTableHolder.width($('table', originalTableHolder).width() + 17);
        // Create a clone of table holder DIV
        var clonedtableHolder = originalTableHolder.clone();

        // Calculate height of all header rows.
        var headerHeight = 0;
        $('thead', originalTableHolder).each(function (index, element) {
            headerHeight = headerHeight + $(element).height();
        });

        // Set the position of cloned table so that cloned table overlapped the original
        clonedtableHolder.css('position', 'relative');
        clonedtableHolder.css('top', headerHeight + 'px');

        // Set the height of cloned header equal to header height only so that body is not visible of cloned header
        clonedtableHolder.height(headerHeight);
        clonedtableHolder.css('overflow', 'hidden');

        // reset the ID attribute of each element in cloned table
        $('*', clonedtableHolder).each(function (index, element) {
            if ($(element).attr('id')) {
                $(element).attr('id', $(element).attr('id') + '_Cloned');
            }
        });

        originalTableHolder.css('border-bottom', '1px solid #aaa');

        // Place the cloned table holder before original one
        originalTableHolder.before(clonedtableHolder);
    });
</script>

and at last below is the CSS class for bit of coloring purpose.

.table-holder
{
    height:200px;
    overflow:auto;
    border-width:0px;    
}

.customerTable thead
{
    background: #4b6c9e;        
    color:White;
}

So the whole idea of this logic is to place the table into a table holder div and create clone of that holder at client side when page loaded. Now hide the body of table inside clone holder and position the remaining header part over to original header.

Same solution also works for asp:gridview, you need to add two more steps to achieve this in gridview,

In OnPrerender event of gridview object in your web page, set the table section of header row equal to TableHeader. if (this.HeaderRow != null) { this.HeaderRow.TableSection = TableRowSection.TableHeader; } And wrap your grid into

.

Note: if your header has clickable controls then you may need to add some more jQuery script to pass the events raised in cloned header to original header. This code is already available in jQuery sticky-header plugin create by jmosbech


+1 for good effort, but for large tables with a lot of data, it is pretty bad to clone the whole thing... I imagine that would double loading time
P
Peter Dow

It's frustrating that what works great in one browser doesn't work in others. The following works in Firefox, but not in Chrome or IE:

<table width="80%">

 <thead>

 <tr>
  <th>Column 1</th>
  <th>Column 2</th>
  <th>Column 3</th>
 </tr>

 </thead>

 <tbody style="height:50px; overflow:auto">

  <tr>
    <td>Cell A1</td>
    <td>Cell B1</td>
    <td>Cell C1</td>
  </tr>

  <tr>
    <td>Cell A2</td>
    <td>Cell B2</td>
    <td>Cell C2</td>
  </tr>

  <tr>
    <td>Cell A3</td>
    <td>Cell B3</td>
    <td>Cell C3</td>
  </tr>

 </tbody>

</table>

I'm trying it in Firefox 8.0, and it doesn't seem to do anything. What does it do?
What is px? Is it that legacy unit used by people on the 90th when a display device was used to have 72 dpi?
I agree: It is very frustrating, Unfortunately browser developers don't respect standards in every situation... and some years ago was even worse! Now some issues have been solved and more browsers behaves in a more standardized way, but a lot of steps still have to be done yet: let's hope and pray :-)