使用jQuery使表格标题在用户滚动时保持固定在顶部。

175
我尝试设计一个 HTML 表格,在用户滚动时,表头将保持在页面顶部,只有当用户滚动它超出视图时才会保留。例如,该表格可能距离页面下方 500 像素,如何使其在用户滚动表头超出视图时(浏览器以某种方式检测到它不再在窗口视图中),其保持在顶部?谁能给我提供一个 JavaScript 的解决方案?

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

在上面的例子中,如果超出视图范围,我希望它随页面滚动。
重要提示:我想要有滚动条(overflow:auto)的解决方案。

2
重复?https://dev59.com/lHNA5IYBdhLWcg3wQ7i4 - Brad Tofel
可能是HTML表格标题始终可见,当查看大型表格时的重复问题。 - Swastik Raj Ghosh
27个回答

146
你可以通过在 window 上绑定 scroll 事件处理程序,并使用另一个带有固定位置的 table 在页面顶部显示标题来实现这样的效果。

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

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

  if (offset >= tableOffset && $fixedHeader.is(":hidden")) {
    $fixedHeader.show();
  } else if (offset < tableOffset) {
    $fixedHeader.hide();
  }
});
#header-fixed {
  position: fixed;
  top: 0px;
  display: none;
  background-color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="table-1">
  <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>
<table id="header-fixed"></table>

当用户向下滚动足够多的时候,这将显示表头以隐藏原始表头。 当用户再次向上滚动页面时,它将再次隐藏。

工作示例:https://jsfiddle.net/andrewwhitaker/fj8wM/


62
我在想,如果标题列的宽度基于内容而改变会怎样?除非列宽是固定的,否则标题行可能与内容行不对齐。这只是一个想法。 - Yzmir Ramirez
16
如果列标题的名称比列数据值短,此示例将无法正常工作。尝试更改该代码段,将值更改为类似于infoooooooo而不是info。我认为在克隆表格时设置某种硬编码宽度可能有所帮助。 - whiterook6
6
这有主要的限制,其中最小的一个是当试图在除 'window' 之外的容器中使用该表时。http://mkoryak.github.io/floatThead/ 提供了一个更通用的解决方案。 - Yuck
13
这可能会帮助需要动态表格列宽的人。这是我最终采用的方式,它是基于 @AndrewWhitaker 的分支: http://jsfiddle.net/noahkoch/wLcjh/1/ - Noah Koch
1
@NoahKoch 使用您的解决方案固定标题仍可能不如原始标题宽,当原始单元格具有填充时。我改进了您的解决方案,以在复制的单元格中添加填充。JSFiddle的分支:http://jsfiddle.net/1n23m69u/2/ - fandasson
显示剩余8条评论

120

纯CSS(不支持IE11):

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

3
我甚至在Chrome浏览器中都看不到这个工作。检查工具显示-webkit-sticky作为position属性的无效值。 - carmenism
@carmenism -webkit-sticky 是为Safari(桌面和iOS)设计的,而不是Chrome。 position: sticky 从Chrome版本56开始可用。您应该设置两个规则:-webkit-stickysticky,并且要与我的代码示例完全相同的顺序。 Safari使用 webkit-sticky,而所有其他浏览器都会用 sticky 覆盖它。 - Ihor Zenich
4
我无法使其正常工作。从快速的谷歌搜索来看,似乎position: sticky与表格元素不兼容。 - rjh
5
适用于我的方法是:在表头中的表格行中添加th{...}。 - nurp
1
你必须将其应用于 th,因为它无法在thead或tr上工作。 - helado
显示剩余7条评论

50

好吧,我试图在不使用固定大小列或为整个表设置固定高度的情况下实现相同的效果。

我想出的解决方案是一个hack。它包括复制整个表格,然后隐藏除了标题以外的所有内容,并使其具有固定位置。

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

请看这里:http://jsfiddle.net/QHQGF/7/

编辑:更新了代码,使得表头可以接收指针事件(因此标头中的按钮和链接仍然有效)。这修复了luhfluh和Joe M报告的问题。

新的 jsfiddle 在这里:http://jsfiddle.net/cjKEx/


3
我最初尝试了那个方法,但问题在于标题中元素的大小取决于表格内容。如果某行内容很大,整列,包括标题,都会变得更宽。如果只克隆thead,您将失去该信息,并且无法获得期望的效果。请参见Yzmir Ramirez对已接受答案的评论。这是我发现的唯一适用于非固定宽度列的方法。我知道它不够干净,这也是我说它是一个hack的原因。 - entropy
1
哦,我明白了。这种方法很棒,因为它可以与流体列一起使用。我查了一下谷歌,没有找到其他具有该功能的方法。无论如何,如果我设法完成它,我会在这里提及它。 - M2_
1
@luhfluh,是的,这个问题是由于在克隆表中使用了pointer-events:none。这是为了使点击穿过克隆并到达原始表。将其恢复到克隆表的标题中,使标题(仅标题)可点击即可解决问题。我已经更新了我的帖子以反映这一点 :) - entropy
4
在<thead><th>上添加边框的问题。 - Sam San
1
@JamVille 因为克隆表具有 visibility: hidden。您只能隐藏 #clone tbody,然后可以设置边框... 工作的 fiddle:http://jsfiddle.net/QHQGF/1707/ - Lajdák Marek
显示剩余9条评论

33
我编写了一个插件来实现这个功能。我已经在这个插件上工作了一年左右,我认为它很好地处理了所有的边缘情况:
  • 在带有溢出容器中滚动
  • 在窗口内滚动
  • 在调整窗口大小时处理
  • 将您的事件绑定到标题
  • 最重要的是,它不会强制您更改表格的CSS样式就能使其正常工作

以下是一些演示/文档:
http://mkoryak.github.io/floatThead/


看起来不错,但如果表格已经运行其他插件(就像我的情况一样),这些插件与您的插件的严格规则冲突,它将无法正常工作。 - masche
它可以与任何缓存其选择器的插件一起使用,这是好的插件应该做的。但是现在没有人这样做了,因为年轻人从未为IE6编写过任何jQuery插件。 - mkoryak
非常好用,还要提一下它在多维表格上的表现非常出色!唯一需要指出的是,当您调用它太快时,表格宽度会有点混乱。但这很容易通过一些回调来避免。 - Tim van Uum
这太棒了,节省了我很多时间,谢谢! - Smithy
我很惊讶人们仍然在使用这个,但他们确实在使用。本周我刚发布了一个新的小版本,修复了2015年某人重新发现的一些错误。 - mkoryak

27

我已经成功解决了更改列宽的问题。 我以Andrew上面的解决方案为起点(非常感谢!),然后添加了一个小循环来设置克隆td的宽度:

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

这种方法可以解决问题,而不必克隆整个表格并隐藏其内容。我对JavaScript和jQuery(以及stack overflow)都很陌生,所以欢迎提出任何意见。


4
工作得很好,但我需要添加以下代码: $("#header-fixed").width($("#table-1").width()); 以使列正确对齐。 - Ilya Fedotov

20

这里已经有很多非常好的解决方案了。但是我在这种情况下使用的最简单的仅限CSS的解决方案如下:

table {
  /* Not required only for visualizing */
  border-collapse: collapse;
  width: 100%;
}

table thead tr th {
  /* Important */
  background-color: red;
  position: sticky;
  z-index: 100;
  top: 0;
}

td {
  /* Not required only for visualizing */
  padding: 1em;
}
<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>
     <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>
     <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>
     <tr>
       <td>info</td>
       <td>info</td>
       <td>info</td>
     </tr>
  </tbody>
</table>

由于JavaScript没有要求,因此情况会更加简化。您只需要关注第二个CSS规则,其中包含确保表头始终保持在顶部的条件。

要详细说明每个规则。 position用于指示浏览器表头对象、其行和单元格都需要粘贴到顶部。这必须与top一起使用,它指定了表头将粘贴到页面或视口的顶部。此外,您可以添加z-index以确保表头内容始终保持在顶部。

背景颜色仅用于说明要点。您无需使用任何其他JavaScript即可获得此效果。这在2016年之后的大多数主要浏览器中都得到支持。


17
这是我发现的最好的固定表头解决方案。
5/11更新:修复了Kerry Johnson指出的水平滚动错误。
Codepen: https://codepen.io/josephting/pen/demELL

;(function($) {
   $.fn.fixMe = function() {
      return this.each(function() {
         var $this = $(this),
            $t_fixed;
         function init() {
            $this.wrap('<div class="container" />');
            $t_fixed = $this.clone();
            $t_fixed.find("tbody").remove().end().addClass("fixed").insertBefore($this);
            resizeFixed();
         }
         function resizeFixed() {
           $t_fixed.width($this.outerWidth());
            $t_fixed.find("th").each(function(index) {
               $(this).css("width",$this.find("th").eq(index).outerWidth()+"px");
            });
         }
         function scrollFixed() {
            var offsetY = $(this).scrollTop(),
            offsetX = $(this).scrollLeft(),
            tableOffsetTop = $this.offset().top,
            tableOffsetBottom = tableOffsetTop + $this.height() - $this.find("thead").height(),
            tableOffsetLeft = $this.offset().left;
            if(offsetY < tableOffsetTop || offsetY > tableOffsetBottom)
               $t_fixed.hide();
            else if(offsetY >= tableOffsetTop && offsetY <= tableOffsetBottom && $t_fixed.is(":hidden"))
               $t_fixed.show();
            $t_fixed.css("left", tableOffsetLeft - offsetX + "px");
         }
         $(window).resize(resizeFixed);
         $(window).scroll(scrollFixed);
         init();
      });
   };
})(jQuery);

$(document).ready(function(){
   $("table").fixMe();
   $(".up").click(function() {
      $('html, body').animate({
      scrollTop: 0
   }, 2000);
 });
});
body{
  font:1.2em normal Arial,sans-serif;
  color:#34495E;
}

h1{
  text-align:center;
  text-transform:uppercase;
  letter-spacing:-2px;
  font-size:2.5em;
  margin:20px 0;
}

.container{
  width:90%;
  margin:auto;
}

table{
  border-collapse:collapse;
  width:100%;
}

.blue{
  border:2px solid #1ABC9C;
}

.blue thead{
  background:#1ABC9C;
}

.purple{
  border:2px solid #9B59B6;
}

.purple thead{
  background:#9B59B6;
}

thead{
  color:white;
}

th,td{
  text-align:center;
  padding:5px 0;
}

tbody tr:nth-child(even){
  background:#ECF0F1;
}

tbody tr:hover{
background:#BDC3C7;
  color:#FFFFFF;
}

.fixed{
  top:0;
  position:fixed;
  width:auto;
  display:none;
  border:none;
}

.scrollMore{
  margin-top:600px;
}

.up{
  cursor:pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1>&darr; SCROLL &darr;</h1>
<table class="blue">
  <thead>
    <tr>
      <th>Colonne 1</th>
      <th>Colonne 2</th>
      <th>Colonne 3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Non</td>
      <td>MaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMaisMais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
       <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
  </tbody>
</table>

<h1 class="scrollMore">&darr; SCROLL MORE &darr;</h1>
<table class="purple">
  <thead>
    <tr>
      <th>Colonne 1</th>
      <th>Colonne 2</th>
      <th>Colonne 3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
       <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
    <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
       <tr>
      <td>Non</td>
      <td>Mais</td>
      <td>Allo !</td>
    </tr>
  </tbody>
</table>
<h1 class="up scrollMore">&uarr; UP &uarr;</h1>


如何动态处理顶部位置。假设我需要滚动到我的主页面固定标题内容下方。**.fixed{top:0;}**。 - VijayVishnu
@VVijay 你可以随时调整固定表头的位置。这里的想法是当页面滚动到您设置的位置时,显示表头的固定副本。请参见 scrollFixed() 函数。 - josephting
是的,我可以在需要的位置显示表格标题的固定副本。但是,它总是显示在顶部,因为有一个 *.fixed {top:0;}*。在一页中,我需要将标题显示在 *Top:0;*,而在另一页中则需要在 top:30px; 上显示。 - VijayVishnu
@VVijay 在这种情况下,您只需要添加其他规则来指定 top:0px;top:30px;,这是基本的CSS和HTML。 - josephting
2
@KerryJohnson 感谢您的留言。我已经进行了一些更改,以支持水平滚动和动态列宽。 - josephting
显示剩余3条评论

8
我发现了一个简单的解决方案,不使用JQuery,只使用CSS。你需要将固定内容放在“th”标签内,并添加CSS。
table th {
    position:sticky;
    top:0;
    z-index:1;
    border-top:0;
    background: #ededed;
}
   

位置属性(position)、层级属性(z-index)和上边距属性(top)就足够了。但是你可以应用其他属性来实现更好的视觉效果。

3
这与两年前发布的Ihor Zenich的答案有何不同? - codepearlex

7

最好的解决方案是使用这个jQuery插件:

https://github.com/jmosbech/StickyTableHeaders

我们尝试了很多其他方案,但是这个插件对我们来说效果非常好。我们在IE、Chrome和Firefox中进行了测试。


你知道任何解决方案,同时保留头部中knockout数据绑定控件的功能吗? - Csaba Toth
对我没用:标题在表格外面并固定在浏览器(窗口)的顶部,即使使用“scrollableArea”选项。我看到这之前已经提到过,但没有其他评论。也许需要使用CSS来使其工作,但该CSS可能不可用。 - Exel Gamboa

5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接