HTML таблица с фиксированными заголовками?

есть ли кросс-браузерный метод CSS / JavaScript для отображения длинной таблицы HTML, чтобы заголовки столбцов оставались фиксированными на экране и не прокручивались с телом таблицы. Подумайте об эффекте "заморозить панели" в Microsoft Excel.

Я хочу иметь возможность прокручивать содержимое таблицы, но всегда иметь возможность видеть заголовки столбцов вверху.

27 ответов


Я искал решение для этого некоторое время и обнаружил, что большинство ответов не работают или не подходят для моей ситуации, поэтому я написал простое решение с помощью jQuery.

это план решения:

  1. клонировать таблицу, которая должна иметь фиксированный заголовок, и место клонированная копия поверх оригинала.
  2. удалить таблицы из верхней части таблицы.
  3. удалите заголовок таблицы из нижней таблицы.
  4. изменить ширина столбца. (Мы отслеживаем исходные ширины столбцов)

Ниже приведен код в демо-версии.

function scrolify(tblAsJQueryObject, height) {
  var oTbl = tblAsJQueryObject;

  // for very large tables you can remove the four lines below
  // and wrap the table with <div> in the mark-up and assign
  // height and overflow property  
  var oTblDiv = $("<div/>");
  oTblDiv.css('height', height);
  oTblDiv.css('overflow', 'scroll');
  oTbl.wrap(oTblDiv);

  // save original width
  oTbl.attr("data-item-original-width", oTbl.width());
  oTbl.find('thead tr td').each(function() {
    $(this).attr("data-item-original-width", $(this).width());
  });
  oTbl.find('tbody tr:eq(0) td').each(function() {
    $(this).attr("data-item-original-width", $(this).width());
  });


  // clone the original table
  var newTbl = oTbl.clone();

  // remove table header from original table
  oTbl.find('thead tr').remove();
  // remove table body from new table
  newTbl.find('tbody tr').remove();

  oTbl.parent().parent().prepend(newTbl);
  newTbl.wrap("<div/>");

  // replace ORIGINAL COLUMN width				
  newTbl.width(newTbl.attr('data-item-original-width'));
  newTbl.find('thead tr td').each(function() {
    $(this).width($(this).attr("data-item-original-width"));
  });
  oTbl.width(oTbl.attr('data-item-original-width'));
  oTbl.find('tbody tr:eq(0) td').each(function() {
    $(this).width($(this).attr("data-item-original-width"));
  });
}

$(document).ready(function() {
  scrolify($('#tblNeedsScrolling'), 160); // 160 is height
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>

<div style="width:300px;border:6px green solid;">
  <table border="1" width="100%" id="tblNeedsScrolling">
    <thead>
      <tr><th>Header 1</th><th>Header 2</th></tr>
    </thead>
    <tbody>
      <tr><td>row 1, cell 1</td><td>row 1, cell 2</td></tr>
      <tr><td>row 2, cell 1</td><td>row 2, cell 2</td></tr>
      <tr><td>row 3, cell 1</td><td>row 3, cell 2</td></tr>
      <tr><td>row 4, cell 1</td><td>row 4, cell 2</td></tr>			
      <tr><td>row 5, cell 1</td><td>row 5, cell 2</td></tr>
      <tr><td>row 6, cell 1</td><td>row 6, cell 2</td></tr>
      <tr><td>row 7, cell 1</td><td>row 7, cell 2</td></tr>
      <tr><td>row 8, cell 1</td><td>row 8, cell 2</td></tr>			
    </tbody>
  </table>
</div>

это решение работает в Chrome и IE. Поскольку он основан на jQuery, это должно работать и в других браузерах, поддерживаемых jQuery.


Это можно чисто разрешить в 4 строках кода.

если вы заботитесь только о современных браузерах, фиксированный заголовок может быть достигнуто гораздо проще с помощью CSS преобразований. Звучит странно, но отлично работает:

  • HTML и CSS остаются как есть.
  • нет внешних зависимостей JS.
  • 4 строки кода.
  • работает для всех конфигураций (table-layout: fixed etc).
document.getElementById("wrap").addEventListener("scroll", function(){
   var translate = "translate(0,"+this.scrollTop+"px)";
   this.querySelector("thead").style.transform = translate;
});

поддержка CSS-преобразований широко доступен за исключением IE8-. Вот полный пример для справки:

document.getElementById("wrap").addEventListener("scroll",function(){
   var translate = "translate(0,"+this.scrollTop+"px)";
   this.querySelector("thead").style.transform = translate;
});
/* your existing container */
#wrap {
    overflow: auto;
    height: 400px;
}

/* css for demo */
td {
    background-color: green;
    width: 200px;
    height: 100px;
}
<div id="wrap">
    <table>
        <thead>
            <tr>
                <th>Foo</th>
                <th>Bar</th>
            </tr>
        </thead>
        <tbody>
            <tr><td></td><td></td></tr>
            <tr><td></td><td></td></tr>
            <tr><td></td><td></td></tr>
            <tr><td></td><td></td></tr>
            <tr><td></td><td></td></tr>
            <tr><td></td><td></td></tr>
            <tr><td></td><td></td></tr>
            <tr><td></td><td></td></tr>
            <tr><td></td><td></td></tr>
            <tr><td></td><td></td></tr>
            <tr><td></td><td></td></tr>
            <tr><td></td><td></td></tr>
        </tbody>
    </table>
</div>

Я только что завершил сборку плагина jQuery, который будет принимать действительную единственную таблицу с использованием действительного HTML (должен иметь thead и tbody) и выведет таблицу с фиксированными заголовками, необязательным фиксированным нижним колонтитулом, который может быть клонированным заголовком или любым контентом, который вы выбрали (разбиение на страницы и т. д.). Если вы хотите воспользоваться большими мониторами, он также изменит размер таблицы при изменении размера браузера. Еще одна добавленная функция - возможность боковой прокрутки, если столбцы таблицы не могут поместиться вид.

http://fixedheadertable.com/

на github:http://markmalek.github.com/Fixed-Header-Table/

Это очень легко настроить, и вы можете создать свои собственные стили для него. Он также использует закругленные углы во всех браузерах. Имейте в виду, что я только что выпустил его, так что технически он все еще бета-версия, и есть очень мало мелких проблем, которые я сглаживаю.

Он работает в Internet Explorer 7, Internet Explorer 8, Safari, Firefox и Chrome.


Я создал плагин, который решает эту проблему. Мой проект -С помощью jQuery.floatThead существует уже более года и является очень зрелым.

Он не требует никаких внешних стилей и не ожидает, что ваша таблица будет стилизована каким-либо определенным образом. Он поддерживает IE8+ и FF / Chrome.

В настоящее время (5/2018) он имеет:

405 коммитов и 998 звезд на github


многие (не все) ответы здесь быстрые хаки, которые, возможно, решили проблему одного человека, но будут работать не для каждого стола.

некоторые из других плагинов старые и, вероятно, отлично работают с IE, но сломаются на FF и chrome.


TL; DR

если вы ориентируетесь на современные браузеры и не имеют экстравагантный потребностям: http://jsfiddle.net/dPixie/byB9d/3/ ... Хотя ... --8-->большая четверка версия довольно сладко, а эта версия обрабатывает ширину жидкости намного лучше.

хорошие новости для всех!

С достижениями HTML5 и CSS3 это теперь возможно, по крайней мере, для современных браузеров. Немного хакерская реализация, которую я придумал, можно найти здесь: http://jsfiddle.net/dPixie/byB9d/3/. Я тестировал его в FX 25, Chrome 31 и IE 10 ...

соответствующий HTML (вставьте HTML5 doctype в верхней части документа, хотя):

<section class="positioned">
  <div class="container">
    <table>
      <thead>
        <tr class="header">
          <th>
            Table attribute name
            <div>Table attribute name</div>
          </th>
          <th>
            Value
            <div>Value</div>
          </th>
          <th>
            Description
            <div>Description</div>
          </th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>align</td>
          <td>left, center, right</td>
          <td>Not supported in HTML5. Deprecated in HTML 4.01. Specifies the alignment of a table according to surrounding text</td>
        </tr>
        <tr>
          <td>bgcolor</td>
          <td>rgb(x,x,x), #xxxxxx, colorname</td>
          <td>Not supported in HTML5. Deprecated in HTML 4.01. Specifies the background color for a table</td>
        </tr>
        <tr>
          <td>border</td>
          <td>1,""</td>
          <td>Specifies whether the table cells should have borders or not</td>
        </tr>
        <tr>
          <td>cellpadding</td>
          <td>pixels</td>
          <td>Not supported in HTML5. Specifies the space between the cell wall and the cell content</td>
        </tr>
        <tr>
          <td>cellspacing</td>
          <td>pixels</td>
          <td>Not supported in HTML5. Specifies the space between cells</td>
        </tr>
        <tr>
          <td>frame</td>
          <td>void, above, below, hsides, lhs, rhs, vsides, box, border</td>
          <td>Not supported in HTML5. Specifies which parts of the outside borders that should be visible</td>
        </tr>
        <tr>
          <td>rules</td>
          <td>none, groups, rows, cols, all</td>
          <td>Not supported in HTML5. Specifies which parts of the inside borders that should be visible</td>
        </tr>
        <tr>
          <td>summary</td>
          <td>text</td>
          <td>Not supported in HTML5. Specifies a summary of the content of a table</td>
        </tr>
        <tr>
          <td>width</td>
          <td>pixels, %</td>
          <td>Not supported in HTML5. Specifies the width of a table</td>
        </tr>
      </tbody>
    </table>
  </div>
</section>

С этим CSS:

html, body{
  margin:0;
  padding:0;
  height:100%;
}
section {
  position: relative;
  border: 1px solid #000;
  padding-top: 37px;
  background: #500;
}
section.positioned {
  position: absolute;
  top:100px;
  left:100px;
  width:800px;
  box-shadow: 0 0 15px #333;
}
.container {
  overflow-y: auto;
  height: 200px;
}
table {
  border-spacing: 0;
  width:100%;
}
td + td {
  border-left:1px solid #eee;
}
td, th {
  border-bottom:1px solid #eee;
  background: #ddd;
  color: #000;
  padding: 10px 25px;
}
th {
  height: 0;
  line-height: 0;
  padding-top: 0;
  padding-bottom: 0;
  color: transparent;
  border: none;
  white-space: nowrap;
}
th div{
  position: absolute;
  background: transparent;
  color: #fff;
  padding: 9px 25px;
  top: 0;
  margin-left: -25px;
  line-height: normal;
  border-left: 1px solid #800;
}
th:first-child div{
  border: none;
}

но как?!

проще говоря, у вас есть заголовок таблицы, который вы визуально скрываете, делая его 0px высоким, который также содержит divs, используемые в качестве фиксированного заголовка. Контейнер таблицы выходит достаточная комната на верхнюю часть к прибавлять на абсолютно позиционированный заголовок, и таблица с полосами прокрутки появляются, как и следовало ожидать.

код выше использует класс positioned для абсолютного позиционирования таблицы (я использую его во всплывающем диалоговом окне стиля), но вы можете использовать его в потоке документа, удалив positioned класса из контейнера.

но ...

это не идеально. Firefox отказывается сделать строку заголовка 0px (по крайней мере, я не нашел никакого способа), но упорно держит ее на минимуме В 4px ... Это не огромная проблема, но в зависимости от вашего стиля, это будет беспорядок с границами и т. д.

таблица также использует подход искусственного столбца, где цвет фона самого контейнера используется в качестве фона для заголовков divs, которые являются прозрачными.

резюме

в целом могут возникнуть проблемы с дизайном в зависимости от ваших требований, особенно границ или сложных фонов. Могут также возникнуть проблемы с вычислимостью, I еще не проверял его в самых разных браузерах (пожалуйста, прокомментируйте свой опыт, если вы попробуете его), но я не нашел ничего подобного, поэтому я подумал, что в любом случае стоит опубликовать ...


все попытки решить это извне спецификации CSS являются бледными тенями того, что мы действительно хотим: доставка по подразумеваемому обещанию THEAD.

эта проблема с замороженными заголовками для таблицы долгое время была открытой раной в HTML/CSS.

в идеальном мире для этой проблемы было бы чисто css-решение. К сожалению, на месте нет ни одного хорошего.

соответствующие стандарты-обсуждение этой темы включить:

обновление: Firefox поставляется позиция: липкий в версии 32. Все выигрывают!


вот плагин jQuery для фиксированного заголовка таблицы. Он позволяет прокручивать всю страницу, замораживая заголовок, когда он достигает вершины. Он хорошо работает с таблицами загрузки twitter.

GitHub repo:https://github.com/oma/table-fixed-header

Это не прокручивать только содержимое таблицы. Искать другие инструменты для этого, как один из этих ответов. Вы сами решаете, что лучше всего подходит вашему случаю.


большинство решений, размещенных здесь, требуют jQuery. Если вы ищете независимое от фреймворка решение, попробуйте Grid:http://www.matts411.com/post/grid/

он размещен на Github здесь:https://github.com/mmurph211/Grid

Он не только поддерживает фиксированные заголовки, он также поддерживает фиксированные левые столбцы и нижние колонтитулы, среди прочего.


более утонченная чистая таблица прокрутки CSS

все чистые решения CSS, которые я видел до сих пор-умные, хотя они могут быть-не имеют определенного уровня полировки или просто не работают правильно в некоторых ситуациях. Поэтому я решил создать свой собственный...

характеристики:

  • это чистый CSS, поэтому не требуется jQuery (или любой javascript вообще, для этого материя)
  • вы можете установить ширину таблицы в процентах (a.к. a. "жидкость") или фиксированное значение, или пусть содержание определяет его ширину (a.к. a. "auto")
  • ширина колонки также может быть жидкой, фиксированной или автоматической.
  • столбцы никогда не будут смещены с заголовками из-за горизонтальной прокрутки (проблема, которая возникает в каждом другом решении на основе CSS, которое я видел, не требует фиксированной ширины).
  • совместимость со всеми популярными настольными браузерами, включая Internet Explorer до версии 8
  • чистый, полированный вид; не небрежный вид 1 пиксель зазоры или неровные границы; выглядит одинаково во всех браузерах

вот несколько скрипок, которые показывают параметры жидкости и автоматической ширины:

  • ширина и высота жидкости (адаптируется к размеру экрана):jsFiddle (обратите внимание, что полоса прокрутки отображается только при необходимости в этой конфигурации, поэтому вам может потребоваться сжать кадр, чтобы увидеть его)

  • Автоматическая Ширина, Фиксированная Высота (легче интегрировать с другим контентом):jsFiddle

автоматическая ширина, фиксированная конфигурация высоты, вероятно, имеет больше вариантов использования, поэтому я опубликую код ниже.

/*the following html and body rule sets are required only if using a % width or height*/
/*html {
  width: 100%;
  height: 100%;
}*/
body {
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0 20px 0 20px;
  text-align: center;
}
.scrollingtable {
  box-sizing: border-box;
  display: inline-block;
  vertical-align: middle;
  overflow: hidden;
  width: auto; /*if you want a fixed width, set it here, else set to auto*/
  min-width: 0/*100%*/; /*if you want a % width, set it here, else set to 0*/
  height: 188px/*100%*/; /*set table height here; can be fixed value or %*/
  min-height: 0/*104px*/; /*if using % height, make this large enough to fit scrollbar arrows + caption + thead*/
  font-family: Verdana, Tahoma, sans-serif;
  font-size: 16px;
  line-height: 20px;
  padding: 20px 0 20px 0; /*need enough padding to make room for caption*/
  text-align: left;
}
.scrollingtable * {box-sizing: border-box;}
.scrollingtable > div {
  position: relative;
  border-top: 1px solid black;
  height: 100%;
  padding-top: 20px; /*this determines column header height*/
}
.scrollingtable > div:before {
  top: 0;
  background: cornflowerblue; /*header row background color*/
}
.scrollingtable > div:before,
.scrollingtable > div > div:after {
  content: "";
  position: absolute;
  z-index: -1;
  width: 100%;
  height: 100%;
  left: 0;
}
.scrollingtable > div > div {
  min-height: 0/*43px*/; /*if using % height, make this large enough to fit scrollbar arrows*/
  max-height: 100%;
  overflow: scroll/*auto*/; /*set to auto if using fixed or % width; else scroll*/
  overflow-x: hidden;
  border: 1px solid black; /*border around table body*/
}
.scrollingtable > div > div:after {background: white;} /*match page background color*/
.scrollingtable > div > div > table {
  width: 100%;
  border-spacing: 0;
  margin-top: -20px; /*inverse of column header height*/
  /*margin-right: 17px;*/ /*uncomment if using % width*/
}
.scrollingtable > div > div > table > caption {
  position: absolute;
  top: -20px; /*inverse of caption height*/
  margin-top: -1px; /*inverse of border-width*/
  width: 100%;
  font-weight: bold;
  text-align: center;
}
.scrollingtable > div > div > table > * > tr > * {padding: 0;}
.scrollingtable > div > div > table > thead {
  vertical-align: bottom;
  white-space: nowrap;
  text-align: center;
}
.scrollingtable > div > div > table > thead > tr > * > div {
  display: inline-block;
  padding: 0 6px 0 6px; /*header cell padding*/
}
.scrollingtable > div > div > table > thead > tr > :first-child:before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  height: 20px; /*match column header height*/
  border-left: 1px solid black; /*leftmost header border*/
}
.scrollingtable > div > div > table > thead > tr > * > div[label]:before,
.scrollingtable > div > div > table > thead > tr > * > div > div:first-child,
.scrollingtable > div > div > table > thead > tr > * + :before {
  position: absolute;
  top: 0;
  white-space: pre-wrap;
  color: white; /*header row font color*/
}
.scrollingtable > div > div > table > thead > tr > * > div[label]:before,
.scrollingtable > div > div > table > thead > tr > * > div[label]:after {content: attr(label);}
.scrollingtable > div > div > table > thead > tr > * + :before {
  content: "";
  display: block;
  min-height: 20px; /*match column header height*/
  padding-top: 1px;
  border-left: 1px solid black; /*borders between header cells*/
}
.scrollingtable .scrollbarhead {float: right;}
.scrollingtable .scrollbarhead:before {
  position: absolute;
  width: 100px;
  top: -1px; /*inverse border-width*/
  background: white; /*match page background color*/
}
.scrollingtable > div > div > table > tbody > tr:after {
  content: "";
  display: table-cell;
  position: relative;
  padding: 0;
  border-top: 1px solid black;
  top: -1px; /*inverse of border width*/
}
.scrollingtable > div > div > table > tbody {vertical-align: top;}
.scrollingtable > div > div > table > tbody > tr {background: white;}
.scrollingtable > div > div > table > tbody > tr > * {
  border-bottom: 1px solid black;
  padding: 0 6px 0 6px;
  height: 20px; /*match column header height*/
}
.scrollingtable > div > div > table > tbody:last-of-type > tr:last-child > * {border-bottom: none;}
.scrollingtable > div > div > table > tbody > tr:nth-child(even) {background: gainsboro;} /*alternate row color*/
.scrollingtable > div > div > table > tbody > tr > * + * {border-left: 1px solid black;} /*borders between body cells*/
<div class="scrollingtable">
  <div>
    <div>
      <table>
        <caption>Top Caption</caption>
        <thead>
          <tr>
            <th><div label="Column 1"/></th>
            <th><div label="Column 2"/></th>
            <th><div label="Column 3"/></th>
            <th>
              <!--more versatile way of doing column label; requires 2 identical copies of label-->
              <div><div>Column 4</div><div>Column 4</div></div>
            </th>
            <th class="scrollbarhead"/> <!--ALWAYS ADD THIS EXTRA CELL AT END OF HEADER ROW-->
          </tr>
        </thead>
        <tbody>
          <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr>
          <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr>
          <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr>
          <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr>
          <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr>
          <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr>
          <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr>
          <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr>
          <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr>
          <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr>
          <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr>
          <tr><td>Lorem ipsum</td><td>Dolor</td><td>Sit</td><td>Amet consectetur</td></tr>
        </tbody>
      </table>
    </div>
    Faux bottom caption
  </div>
</div>

<!--[if lte IE 9]><style>.scrollingtable > div > div > table {margin-right: 17px;}</style><![endif]-->

метод, который я использовал для замораживания строки заголовка, похож на d-Pixie, поэтому обратитесь к его сообщению для объяснения. Было множество ошибок и ограничений с этой техникой, которые могли быть исправлены только с кучей дополнительный CSS и дополнительный контейнер div или два.


простой плагин jQuery

это вариант решения Маэ. Вы можете назвать это как $('table#foo').scrollableTable();

идея:

  • разделить thead и tbody в отдельную table элементов
  • сделать их ширины ячеек совпадают снова
  • оберните второй table на div.scrollable
  • используйте CSS, чтобы сделать div.scrollable на самом деле свиток

CSS может быть:

div.scrollable { height: 300px; overflow-y: scroll;}

предостережения

  • очевидно, что разделение этих таблиц делает разметку менее семантической. Я не уверен, как это повлияет на доступность.
  • этот плагин не имеет дело с нижними колонтитулами, несколькими заголовками и т. д.
  • я тестировал его только в Chrome версии 20.

тем не менее, он работает для моих целей, и вы можете взять и изменить его.

вот плагин:

jQuery.fn.scrollableTable = function () {
  var $newTable, $oldTable, $scrollableDiv, originalWidths;
  $oldTable = $(this);

  // Once the tables are split, their cell widths may change. 
  // Grab these so we can make the two tables match again.
  originalWidths = $oldTable.find('tr:first td').map(function() {
    return $(this).width();
  });

  $newTable = $oldTable.clone();
  $oldTable.find('tbody').remove();
  $newTable.find('thead').remove();

  $.each([$oldTable, $newTable], function(index, $table) {
    $table.find('tr:first td').each(function(i) {
      $(this).width(originalWidths[i]);
    });
  });

  $scrollableDiv = $('<div/>').addClass('scrollable');
  $newTable.insertAfter($oldTable).wrap($scrollableDiv);
};

:)

Не очень чистое, но чистое решение HTML/CSS.

table {
    overflow-x:scroll;
}

tbody {
    max-height: /*your desired max height*/
    overflow-y:scroll;
    display:block;
}

обновлено для IE8+ пример JSFiddle


поддержка фиксированного нижнего колонтитула

я расширил функцию Натана, чтобы также поддерживать фиксированный нижний колонтитул и максимальную высоту. Кроме того, функция установит сам css,вам нужно только поддерживать ширину.

использование:

фиксированная высота:

$('table').scrollableTable({ height: 100 });

Max height (если браузер поддерживает опцию css "max-height"):

$('table').scrollableTable({ maxHeight: 100 });

сценарий:

jQuery.fn.scrollableTable = function(options) {

    var $originalTable, $headTable, $bodyTable, $footTable, $scrollableDiv, originalWidths;

    // prepare the separate parts of the table
    $originalTable = $(this);
    $headTable = $originalTable.clone();

    $headTable.find('tbody').remove();
    $headTable.find('tfoot').remove();

    $bodyTable = $originalTable.clone();
    $bodyTable.find('thead').remove();
    $bodyTable.find('tfoot').remove();

    $footTable = $originalTable.clone();
    $footTable.find('thead').remove();
    $footTable.find('tbody').remove();

    // grap original column widths and set them in the separate tables
    originalWidths = $originalTable.find('tr:first td').map(function() {
        return $(this).width();
    });

    $.each([$headTable, $bodyTable, $footTable], function(index, $table) {
        $table.find('tr:first td').each(function(i) {
            $(this).width(originalWidths[i]);
        });
    });

    // the div that makes the body table scroll
    $scrollableDiv = $('<div/>').css({
        'overflow-y': 'scroll'
    });

    if(options.height) {
        $scrollableDiv.css({'height': options.height});
    }
    else if(options.maxHeight) {
        $scrollableDiv.css({'max-height': options.maxHeight});
    }

    // add the new separate tables and remove the original one
    $headTable.insertAfter($originalTable);
    $bodyTable.insertAfter($headTable);
    $footTable.insertAfter($bodyTable);
    $bodyTable.wrap($scrollableDiv);
    $originalTable.remove();

};

два divs, один для заголовка, один для данных. Сделайте данные div прокручиваемыми и используйте JavaScript, чтобы установить ширину столбцов в заголовке такой же, как ширина в данных. Я думаю, что ширина столбцов данных должна быть фиксированной, а не динамической.


Я понимаю, что вопрос позволяет JavaScript, но вот чистое решение CSS, которое я разработал, которое также позволяет таблице расширяться по горизонтали. Протестировано с IE10 и последними браузерами Chrome и Firefox. Ссылка на jsFiddle находится внизу.

HTML:

Putting some text here to differentiate between the header aligning with the top of the screen and the header aligning with the top of one of it's ancestor containers.
<div id="positioning-container">
<div id="scroll-container">
    <table>
        <colgroup>
            <col class="col1"></col>
            <col class="col2"></col>
        </colgroup>
        <thead>
            <th class="header-col1"><div>Header 1</div></th>
            <th class="header-col2"><div>Header 2</div></th>
        </thead>
        <tbody>
            <tr><td>Cell 1.1</td><td>Cell 1.2</td></tr>
            <tr><td>Cell 2.1</td><td>Cell 2.2</td></tr>
            <tr><td>Cell 3.1</td><td>Cell 3.2</td></tr>
            <tr><td>Cell 4.1</td><td>Cell 4.2</td></tr>
            <tr><td>Cell 5.1</td><td>Cell 5.2</td></tr>
            <tr><td>Cell 6.1</td><td>Cell 6.2</td></tr>
            <tr><td>Cell 7.1</td><td>Cell 7.2</td></tr>

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

и CSS:

table{
    border-collapse: collapse;
    table-layout: fixed;
    width: 100%;
}
/* Not required, just helps with alignment for this example */
td, th{
    padding: 0;
    margin: 0;
}

tbody{
    background-color: #ddf;
}

thead {
    /* Keeps the header in place. Don't forget top: 0 */
    position: absolute;
    top: 0;
    background-color: #ddd;
    /* The 17px is to adjust for the scrollbar width.
     * This is a new css value that makes this pure
     * css example possible */
    width: calc(100% - 17px);
    height: 20px;
}
/* Positioning container. Required to position the
 * header since the header uses position:absolute
 * (otherwise it would position at the top of the screen) */
#positioning-container{
    position: relative;
}
/* A container to set the scroll-bar and 
 * includes padding to move the table contents
 * down below the header (padding = header height) */
#scroll-container{
    overflow-y: auto;
    padding-top: 20px;
    height: 100px;
}
.header-col1{
    background-color: red;
}
/* fixed width header columns need a div to set their width */
.header-col1 div{
    width: 100px;
}
/* expandable columns need a width set on the th tag */
.header-col2{
    width: 100%;
}
.col1 {
    width: 100px;
}
.col2{
    width: 100%;
}

http://jsfiddle.net/HNHRv/3/


для тех, кто попробовал хорошее решение, данное Максимилианом Хилсом, и не удалось заставить его работать с Internet Explorer, у меня была та же проблема (т. е. 11) и узнал, в чем проблема.

в IE 11 преобразование стиля (по крайней мере, с переводом) не работает на <THEAD>. Я решил это, вместо этого применив стиль ко всем <TH> в цикле. Это сработало. Мой javascript выглядит так

document.getElementById('pnlGridWrap').addEventListener("scroll", function () {
  var translate = "translate(0," + this.scrollTop + "px)";
  var myElements = this.querySelectorAll("th");
  for (var i = 0; i < myElements.length; i++) {
    myElements[i].style.transform=translate;
  }
});

в моем случае таблица была GridView в ASP.NET - ... Сначала я подумал, что это потому, что у него нет <THEAD>, но даже когда я заставлял его есть, он не работал. Затем я узнал, что написал выше.

Это очень хорошее и простое решение. На Chrome он идеален, на Firefox немного отрывистый, а на IE еще более отрывистый. Но в целом это хорошее решение.


свойство CSS position: sticky имеет большую поддержку в большинстве современных браузеров (у меня были проблемы с ребром, см. ниже).

Это позволяет нам решить проблему фиксированных заголовков довольно легко:

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

Safari нужен префикс поставщика:-webkit-sticky.

для Firefox, мне пришлось добавить min-height: 0 один из родительских элементов. Я забыл, зачем это было нужно.

к сожалению, реализация Microsoft Edge, похоже, работает только наполовину. По крайней мере, я некоторые мерцающие и несоосные ячейки таблицы в моем тестировании. Стол все еще можно было использовать, но у него были значительные эстетические проблемы.


используйте последнюю версию jQuery и включите следующий код javascript

$(window).scroll(function(){         
  $("id of the div element").offset({top:$(window).scrollTop()});         
}); 

Это не точное решение для фиксированной строки заголовка, но я создал довольно оригинальный метод повторения строки заголовка по всей длинной таблице, но все еще сохраняя возможность сортировки. Этот аккуратный маленький вариант требует jQuery tablesorter плагин. Вот как это работает:

HTML-код

<table class="tablesorter boxlist" id="pmtable">
<thead class="fixedheader">
        <tr class="boxheadrow">
    <th width="70px" class="header">Job Number</th>
    <th width="10px" class="header">Pri</th>
    <th width="70px" class="header">CLLI</th>
    <th width="35px" class="header">Market</th>
    <th width="35px" class="header">Job Status</th>
    <th width="65px" class="header">Technology</th>
    <th width="95px;" class="header headerSortDown">MEI</th>
    <th width="95px" class="header">TEO Writer</th>
    <th width="75px" class="header">Quote Due</th>
    <th width="100px" class="header">Engineer</th>
    <th width="75px" class="header">ML Due</th>
    <th width="75px" class="header">ML Complete</th>
    <th width="75px" class="header">SPEC Due</th>
    <th width="75px" class="header">SPEC Complete</th>
    <th width="100px" class="header">Install Supervisor</th>
    <th width="75px" class="header">MasTec OJD</th>
    <th width="75px" class="header">Install Start</th>
    <th width="30px" class="header">Install Hours</th>
    <th width="75px" class="header">Revised CRCD</th>
    <th width="75px" class="header">Latest Ship-To-Site</th>
    <th width="30px" class="header">Total Parts</th>
    <th width="30px" class="header">OEM Rcvd</th>
    <th width="30px" class="header">Minor Rcvd</th>
    <th width="30px" class="header">Total Received</th>
    <th width="30px" class="header">% On Site</th>
    <th width="60px" class="header">Actions</th>
    </tr>
</thead>
<tbody class="scrollable">
        <tr data-job_id="3548" data-ml_id="" class="odd">
            <td class="c black">FL-8-RG9UP</td>
            <td data-pri="2" class="priority c yellow">M</td>
            <td class="c">FTLDFLOV</td>
            <td class="c">SFL</td>
            <td class="c">NOI</td>
            <td class="c">TRANSPORT</td>
            <td class="c"></td>
            <td class="c">Chris Byrd</td>
            <td class="c">Apr 13, 2013</td>
            <td class="c">Kris Hall</td>
            <td class="c">May 20, 2013</td>
            <td class="c">May 20, 2013</td>
            <td class="c">Jun 5, 2013</td>
            <td class="c">Jun 7, 2013</td>
            <td class="c">Joseph Fitz</td>
            <td class="c">Jun 10, 2013</td>
            <td class="c">TBD</td>
            <td class="c">123</td>
            <td class="c revised_crcd"><input readonly="true" name="revised_crcd" value="Jul 26, 2013" type="text" size="12" class="smInput r_crcd c hasDatepicker" id="dp1377194058616"></td>
            <td class="c">TBD</td>
            <td class="c">N/A</td>
            <td class="c">N/A</td>
            <td class="c">N/A</td>
            <td class="c">N/A</td>
            <td class="c">N/A</td>
            <td class="actions"><span style="float:left;" class="ui-icon ui-icon-folder-open editJob" title="View this job" s="" details'=""></span></td>
        </tr>
        <tr data-job_id="4264" data-ml_id="2959" class="even">
            <td class="c black">MTS13009SF</td>
            <td data-pri="2" class="priority c yellow">M</td>
            <td class="c">OJUSFLTL</td>
            <td class="c">SFL</td>
            <td class="c">NOI</td>
            <td class="c">TRANSPORT</td>
            <td class="c"></td>
            <td class="c">DeMarcus Stewart</td>
            <td class="c">May 22, 2013</td>
            <td class="c">Ryan Alsobrook</td>
            <td class="c">Jun 19, 2013</td>
            <td class="c">Jun 27, 2013</td>
            <td class="c">Jun 19, 2013</td>
            <td class="c">Jul 4, 2013</td>
            <td class="c">Randy Williams</td>
            <td class="c">Jun 21, 2013</td>
            <td class="c">TBD</td>
            <td class="c">95</td>
            <td class="c revised_crcd"><input readonly="true" name="revised_crcd" value="Aug 9, 2013" type="text" size="12" class="smInput r_crcd c hasDatepicker" id="dp1377194058632"></td><td class="c">TBD</td>
            <td class="c">0</td>
            <td class="c">0.00%</td>
            <td class="c">0.00%</td>
            <td class="c">0.00%</td>
            <td class="c">0.00%</td>
            <td class="actions"><span style="float:left;" class="ui-icon ui-icon-folder-open editJob" title="View this job" s="" details'=""></span><input style="float:left;" type="hidden" name="req_ship" class="reqShip hasDatepicker" id="dp1377194058464"><span style="float:left;" class="ui-icon ui-icon-calendar requestShip" title="Schedule this job for shipping"></span><span class="ui-icon ui-icon-info viewOrderInfo" style="float:left;" title="Show material details for this order"></span></td>
        </tr>
        .
        .
        .
        .
        <tr class="boxheadrow repeated-header">
        <th width="70px" class="header">Job Number</th>
    <th width="10px" class="header">Pri</th>
    <th width="70px" class="header">CLLI</th>
    <th width="35px" class="header">Market</th>
    <th width="35px" class="header">Job Status</th>
    <th width="65px" class="header">Technology</th>
    <th width="95px;" class="header">MEI</th>
    <th width="95px" class="header">TEO Writer</th>
    <th width="75px" class="header">Quote Due</th>
    <th width="100px" class="header">Engineer</th>
    <th width="75px" class="header">ML Due</th>
    <th width="75px" class="header">ML Complete</th>
    <th width="75px" class="header">SPEC Due</th>
    <th width="75px" class="header">SPEC Complete</th>
    <th width="100px" class="header">Install Supervisor</th>
    <th width="75px" class="header">MasTec OJD</th>
    <th width="75px" class="header">Install Start</th>
    <th width="30px" class="header">Install Hours</th>
    <th width="75px" class="header">Revised CRCD</th>
    <th width="75px" class="header">Latest Ship-To-Site</th>
    <th width="30px" class="header">Total Parts</th>
    <th width="30px" class="header">OEM Rcvd</th>
    <th width="30px" class="header">Minor Rcvd</th>
    <th width="30px" class="header">Total Received</th>
    <th width="30px" class="header">% On Site</th>
    <th width="60px" class="header">Actions</th>
    </tr>

Obvioiusly, моя таблица имеет гораздо больше строк, чем этот. 193 чтобы быть точным, nut вы можете увидеть, где строка заголовка повторяется. Повторение строка заголовка настраивается этой функцией:

jQuery

    // clone the original header row and add the "repeated-header" class
var tblHeader = $('tr.boxheadrow').clone().addClass('repeated-header');
    // add the cloned header with the new class every 34th row (or as you see fit)
$('tbody tr:odd:nth-of-type(17n)').after(tblHeader);
    // on the 'sortStart' routine, remove all the inserted header rows
$('#pmtable').bind('sortStart', function() {
    $('.repeated-header').remove();
    // on the 'sortEnd' routine, add back all the header row lines.
}).bind('sortEnd', function() {
    $('tbody tr:odd:nth-of-type(17n)').after(tblHeader);
});

Я хотел бы найти решение @Mark раньше, но я пошел и написал свой собственный, прежде чем увидел этот вопрос...

Mine - очень легкий плагин jQuery, который поддерживает фиксированный верхний, нижний колонтитул, охват столбцов (colspan), изменение размера, горизонтальную прокрутку и дополнительное количество строк для отображения перед началом прокрутки.

С помощью jQuery.scrollTableBody (GitHub)

пока у вас есть таблица с правильной <thead>, <tbody> и (опционально) <tfoot>, все, что вам нужно сделать это:

$('table').scrollTableBody();

Я разработал простой легкий плагин jQuery для преобразования таблицы HTML в прокручиваемую таблицу с фиксированным заголовком таблицы и столбцами.

плагин хорошо работает, чтобы соответствовать пиксель к пикселю позиционирования фиксированной секции с прокручиваемой секцией. Кроме того, вы также можете заморозить количество столбцов, которые всегда будут отображаться при прокрутке по горизонтали.

Демо И Документация: http://meetselva.github.io/fixed-table-rows-cols/

РЕПО Github: https://github.com/meetselva/fixed-table-rows-cols

Ниже приведено использование простой таблицы с фиксированным заголовком,

$(<table selector>).fxdHdrCol({
    width:     "100%",
    height:    200,
    colModal: [{width: 30, align: 'center'},
               {width: 70, align: 'center'}, 
               {width: 200, align: 'left'}, 
               {width: 100, align: 'center'}, 
               {width: 70, align: 'center'}, 
               {width: 250, align: 'center'}
              ]
});

многие люди, похоже, ищут этот ответ, я нашел его похороненным в ответе на другой вопрос здесь:синхронизация ширины столбца между таблицами в двух разных кадрах и т. д.

из десятков методов, которые я пробовал, это единственный метод, который я нашел, который работает надежно, чтобы позволить вам иметь прокрутку нижней таблицы с таблицей заголовка, имеющей одинаковую ширину.

вот как я это сделал, сначала я улучшил jsfiddle выше, чтобы создать это функция, которая работает как на td, так и на th (в случае, если это спотыкается о других, которые используют th для стиля своих строк заголовка).

var setHeaderTableWidth= function (headertableid,basetableid) {
            $("#"+headertableid).width($("#"+basetableid).width());
            $("#"+headertableid+" tr th").each(function (i) {
                $(this).width($($("#"+basetableid+" tr:first td")[i]).width());
            });
            $("#" + headertableid + " tr td").each(function (i) {
                $(this).width($($("#" + basetableid + " tr:first td")[i]).width());
            });
        }

Далее, вам нужно создать две таблицы, обратите внимание, что таблица заголовка должна иметь дополнительный TD, чтобы оставить место в верхней таблице для полосы прокрутки, например:

 <table id="headertable1" class="input-cells table-striped">
        <thead>
            <tr style="background-color:darkgray;color:white;"><th>header1</th><th>header2</th><th>header3</th><th>header4</th><th>header5</th><th>header6</th><th></th></tr>
        </thead>
     </table>
    <div id="resizeToBottom" style="overflow-y:scroll;overflow-x:hidden;">
        <table id="basetable1" class="input-cells table-striped">
            <tbody >
                <tr>
                    <td>testdata</td>
                    <td>2</td>
                    <td>3</td>
                    <td>4</span></td>
                    <td>55555555555555</td>
                    <td>test</td></tr>
            </tbody>
        </table>
    </div>

тогда сделайте что-нибудь вроде:

        setHeaderTableWidth('headertable1', 'basetable1');
        $(window).resize(function () {
            setHeaderTableWidth('headertable1', 'basetable1');
        });

Это единственное решение, которое я нашел в stackoverflow, которое работает из многих подобных вопросов, которые были опубликованы, которое работает в все мои дела.

например, я попробовал плагин jQuery stickytables, который не работает с durandal, и проект кода google здесь https://code.google.com/p/js-scroll-table-header/issues/detail?id=2

другие решения, связанные с клонированием таблиц, имеют низкую производительность или сосут и не работают во всех случаях.

НЕТ НЕОБХОДИМОСТИ В ЭТИХ СЛИШКОМ СЛОЖНЫХ РЕШЕНИЯХ, ПРОСТО СДЕЛАЙТЕ ДВЕ ТАБЛИЦЫ, КАК ПОКАЗАНО НИЖЕ, И ВЫЗОВИТЕ setHeaderTableWidth функция, как описано здесь и бум, вы сделали.

Если это не работает для вас, вы, вероятно, играли со своим свойством CSS box-sizing, и вам нужно установить его правильно. Легко испортить ваш css случайно есть много вещей,которые могут пойти не так, так что просто быть в курсе / осторожны. ЭТОТ ПОДХОД РАБОТАЕТ ДЛЯ МЕНЯ. Мне любопытно, если это работает для других, дайте мне знать. Удачи!


вот решение, с которым мы закончили работу (для того, чтобы иметь дело с некоторыми крайними случаями и более старыми версиями IE, мы в конечном итоге также исчезли в строке заголовка на прокрутке, а затем снова исчезли, когда прокрутка заканчивается, но в браузерах Firefox и Webkit это решение работает. Он предполагает border-collapse: коллапс.

ключ к этому решению заключается в том, что после применения границы-крах, преобразования CSS работа над заголовком, Так что это просто вопрос перехвата событий прокрутки и правильной настройки преобразования. Вам не нужно ничего дублировать. Если это поведение не реализовано должным образом в браузере, трудно представить себе более легкое решение.

JSFiddle:http://jsfiddle.net/podperson/tH9VU/2/

он реализован как простой плагин jQuery. Вы просто делаете свой thead липким с вызовом, таким как $('thead').sticky () и они будут болтаться вокруг. Работает для нескольких таблиц на странице и заголовках разделов посередине больших столов.

        $.fn.sticky = function(){
        $(this).each( function(){
            var thead = $(this),
                tbody = thead.next('tbody');

            updateHeaderPosition();

            function updateHeaderPosition(){
                if( 
                    thead.offset().top < $(document).scrollTop()
                    && tbody.offset().top + tbody.height() > $(document).scrollTop()
                ){
                    var tr = tbody.find('tr').last(),
                        y = tr.offset().top - thead.height() < $(document).scrollTop()
                            ? tr.offset().top - thead.height() - thead.offset().top
                            : $(document).scrollTop() - thead.offset().top;

                    thead.find('th').css({
                        'z-index': 100,
                        'transform': 'translateY(' + y + 'px)',
                        '-webkit-transform': 'translateY(' + y + 'px)'
                    });
                } else {
                    thead.find('th').css({
                        'transform': 'none',
                        '-webkit-transform': 'none'
                    });
                }
            }

            // see http://www.quirksmode.org/dom/events/scroll.html
            $(window).on('scroll', updateHeaderPosition);
        });
    }

    $('thead').sticky();

Я нашел этот обходной путь-переместить строку заголовка в таблице выше таблицы с данными:

<html>
<head>
	<title>Fixed header</title>
	<style>
		table td {width:75px;}
	</style>
</head>

<body>
<div style="height:auto; width:350px; overflow:auto">
<table border="1">
<tr>
	<td>header 1</td>
	<td>header 2</td>
	<td>header 3</td>
</tr>
</table>
</div>

<div style="height:50px; width:350px; overflow:auto">
<table border="1">
<tr>
	<td>row 1 col 1</td>
	<td>row 1 col 2</td>
	<td>row 1 col 3</td>		
</tr>
<tr>
	<td>row 2 col 1</td>
	<td>row 2 col 2</td>
	<td>row 2 col 3</td>		
</tr>
<tr>
	<td>row 3 col 1</td>
	<td>row 3 col 2</td>
	<td>row 3 col 3</td>		
</tr>
<tr>
	<td>row 4 col 1</td>
	<td>row 4 col 2</td>
	<td>row 4 col 3</td>		
</tr>
<tr>
	<td>row 5 col 1</td>
	<td>row 5 col 2</td>
	<td>row 5 col 3</td>		
</tr>
<tr>
	<td>row 6 col 1</td>
	<td>row 6 col 2</td>
	<td>row 6 col 3</td>		
</tr>
</table>
</div>


</body>
</html>

С применением StickyTableHeaders плагин jQuery для таблицы, заголовки столбцов будут придерживаться верхней части окна просмотра при прокрутке вниз.

пример:

$(function () {
    $("table").stickyTableHeaders();
});

/*! Copyright (c) 2011 by Jonas Mosbech - https://github.com/jmosbech/StickyTableHeaders
	MIT license info: https://github.com/jmosbech/StickyTableHeaders/blob/master/license.txt */

;
(function ($, window, undefined) {
    'use strict';

    var name = 'stickyTableHeaders',
        id = 0,
        defaults = {
            fixedOffset: 0,
            leftOffset: 0,
            marginTop: 0,
            scrollableArea: window
        };

    function Plugin(el, options) {
        // To avoid scope issues, use 'base' instead of 'this'
        // to reference this class from internal events and functions.
        var base = this;

        // Access to jQuery and DOM versions of element
        base.$el = $(el);
        base.el = el;
        base.id = id++;
        base.$window = $(window);
        base.$document = $(document);

        // Listen for destroyed, call teardown
        base.$el.bind('destroyed',
        $.proxy(base.teardown, base));

        // Cache DOM refs for performance reasons
        base.$clonedHeader = null;
        base.$originalHeader = null;

        // Keep track of state
        base.isSticky = false;
        base.hasBeenSticky = false;
        base.leftOffset = null;
        base.topOffset = null;

        base.init = function () {
            base.$el.each(function () {
                var $this = $(this);

                // remove padding on <table> to fix issue #7
                $this.css('padding', 0);

                base.$originalHeader = $('thead:first', this);
                base.$clonedHeader = base.$originalHeader.clone();
                $this.trigger('clonedHeader.' + name, [base.$clonedHeader]);

                base.$clonedHeader.addClass('tableFloatingHeader');
                base.$clonedHeader.css('display', 'none');

                base.$originalHeader.addClass('tableFloatingHeaderOriginal');

                base.$originalHeader.after(base.$clonedHeader);

                base.$printStyle = $('<style type="text/css" media="print">' +
                    '.tableFloatingHeader{display:none !important;}' +
                    '.tableFloatingHeaderOriginal{position:static !important;}' +
                    '</style>');
                $('head').append(base.$printStyle);
            });

            base.setOptions(options);
            base.updateWidth();
            base.toggleHeaders();
            base.bind();
        };

        base.destroy = function () {
            base.$el.unbind('destroyed', base.teardown);
            base.teardown();
        };

        base.teardown = function () {
            if (base.isSticky) {
                base.$originalHeader.css('position', 'static');
            }
            $.removeData(base.el, 'plugin_' + name);
            base.unbind();

            base.$clonedHeader.remove();
            base.$originalHeader.removeClass('tableFloatingHeaderOriginal');
            base.$originalHeader.css('visibility', 'visible');
            base.$printStyle.remove();

            base.el = null;
            base.$el = null;
        };

        base.bind = function () {
            base.$scrollableArea.on('scroll.' + name, base.toggleHeaders);
            if (!base.isWindowScrolling) {
                base.$window.on('scroll.' + name + base.id, base.setPositionValues);
                base.$window.on('resize.' + name + base.id, base.toggleHeaders);
            }
            base.$scrollableArea.on('resize.' + name, base.toggleHeaders);
            base.$scrollableArea.on('resize.' + name, base.updateWidth);
        };

        base.unbind = function () {
            // unbind window events by specifying handle so we don't remove too much
            base.$scrollableArea.off('.' + name, base.toggleHeaders);
            if (!base.isWindowScrolling) {
                base.$window.off('.' + name + base.id, base.setPositionValues);
                base.$window.off('.' + name + base.id, base.toggleHeaders);
            }
            base.$scrollableArea.off('.' + name, base.updateWidth);
        };

        base.toggleHeaders = function () {
            if (base.$el) {
                base.$el.each(function () {
                    var $this = $(this),
                        newLeft,
                        newTopOffset = base.isWindowScrolling ? (
                        isNaN(base.options.fixedOffset) ? base.options.fixedOffset.outerHeight() : base.options.fixedOffset) : base.$scrollableArea.offset().top + (!isNaN(base.options.fixedOffset) ? base.options.fixedOffset : 0),
                        offset = $this.offset(),

                        scrollTop = base.$scrollableArea.scrollTop() + newTopOffset,
                        scrollLeft = base.$scrollableArea.scrollLeft(),

                        scrolledPastTop = base.isWindowScrolling ? scrollTop > offset.top : newTopOffset > offset.top,
                        notScrolledPastBottom = (base.isWindowScrolling ? scrollTop : 0) < (offset.top + $this.height() - base.$clonedHeader.height() - (base.isWindowScrolling ? 0 : newTopOffset));

                    if (scrolledPastTop && notScrolledPastBottom) {
                        newLeft = offset.left - scrollLeft + base.options.leftOffset;
                        base.$originalHeader.css({
                            'position': 'fixed',
                                'margin-top': base.options.marginTop,
                                'left': newLeft,
                                'z-index': 3 // #18: opacity bug
                        });
                        base.leftOffset = newLeft;
                        base.topOffset = newTopOffset;
                        base.$clonedHeader.css('display', '');
                        if (!base.isSticky) {
                            base.isSticky = true;
                            // make sure the width is correct: the user might have resized the browser while in static mode
                            base.updateWidth();
                        }
                        base.setPositionValues();
                    } else if (base.isSticky) {
                        base.$originalHeader.css('position', 'static');
                        base.$clonedHeader.css('display', 'none');
                        base.isSticky = false;
                        base.resetWidth($('td,th', base.$clonedHeader), $('td,th', base.$originalHeader));
                    }
                });
            }
        };

        base.setPositionValues = function () {
            var winScrollTop = base.$window.scrollTop(),
                winScrollLeft = base.$window.scrollLeft();
            if (!base.isSticky || winScrollTop < 0 || winScrollTop + base.$window.height() > base.$document.height() || winScrollLeft < 0 || winScrollLeft + base.$window.width() > base.$document.width()) {
                return;
            }
            base.$originalHeader.css({
                'top': base.topOffset - (base.isWindowScrolling ? 0 : winScrollTop),
                    'left': base.leftOffset - (base.isWindowScrolling ? 0 : winScrollLeft)
            });
        };

        base.updateWidth = function () {
            if (!base.isSticky) {
                return;
            }
            // Copy cell widths from clone
            if (!base.$originalHeaderCells) {
                base.$originalHeaderCells = $('th,td', base.$originalHeader);
            }
            if (!base.$clonedHeaderCells) {
                base.$clonedHeaderCells = $('th,td', base.$clonedHeader);
            }
            var cellWidths = base.getWidth(base.$clonedHeaderCells);
            base.setWidth(cellWidths, base.$clonedHeaderCells, base.$originalHeaderCells);

            // Copy row width from whole table
            base.$originalHeader.css('width', base.$clonedHeader.width());
        };

        base.getWidth = function ($clonedHeaders) {
            var widths = [];
            $clonedHeaders.each(function (index) {
                var width, $this = $(this);

                if ($this.css('box-sizing') === 'border-box') {
                    width = $this[0].getBoundingClientRect().width; // #39: border-box bug
                } else {
                    var $origTh = $('th', base.$originalHeader);
                    if ($origTh.css('border-collapse') === 'collapse') {
                        if (window.getComputedStyle) {
                            width = parseFloat(window.getComputedStyle(this, null).width);
                        } else {
                            // ie8 only
                            var leftPadding = parseFloat($this.css('padding-left'));
                            var rightPadding = parseFloat($this.css('padding-right'));
                            // Needs more investigation - this is assuming constant border around this cell and it's neighbours.
                            var border = parseFloat($this.css('border-width'));
                            width = $this.outerWidth() - leftPadding - rightPadding - border;
                        }
                    } else {
                        width = $this.width();
                    }
                }

                widths[index] = width;
            });
            return widths;
        };

        base.setWidth = function (widths, $clonedHeaders, $origHeaders) {
            $clonedHeaders.each(function (index) {
                var width = widths[index];
                $origHeaders.eq(index).css({
                    'min-width': width,
                        'max-width': width
                });
            });
        };

        base.resetWidth = function ($clonedHeaders, $origHeaders) {
            $clonedHeaders.each(function (index) {
                var $this = $(this);
                $origHeaders.eq(index).css({
                    'min-width': $this.css('min-width'),
                        'max-width': $this.css('max-width')
                });
            });
        };

        base.setOptions = function (options) {
            base.options = $.extend({}, defaults, options);
            base.$scrollableArea = $(base.options.scrollableArea);
            base.isWindowScrolling = base.$scrollableArea[0] === window;
        };

        base.updateOptions = function (options) {
            base.setOptions(options);
            // scrollableArea might have changed
            base.unbind();
            base.bind();
            base.updateWidth();
            base.toggleHeaders();
        };

        // Run initializer
        base.init();
    }

    // A plugin wrapper around the constructor,
    // preventing against multiple instantiations
    $.fn[name] = function (options) {
        return this.each(function () {
            var instance = $.data(this, 'plugin_' + name);
            if (instance) {
                if (typeof options === 'string') {
                    instance[options].apply(instance);
                } else {
                    instance.updateOptions(options);
                }
            } else if (options !== 'destroy') {
                $.data(this, 'plugin_' + name, new Plugin(this, options));
            }
        });
    };

})(jQuery, window);
body {
    margin: 0 auto;
    padding: 0 20px;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 11px;
    color: #555;
}
table {
    border: 0;
    padding: 0;
    margin: 0 0 20px 0;
    border-collapse: collapse;
}
th {
    padding: 5px;
    /* NOTE: th padding must be set explicitly in order to support IE */
    text-align: right;
    font-weight:bold;
    line-height: 2em;
    color: #FFF;
    background-color: #555;
}
tbody td {
    padding: 10px;
    line-height: 18px;
    border-top: 1px solid #E0E0E0;
}
tbody tr:nth-child(2n) {
    background-color: #F7F7F7;
}
tbody tr:hover {
    background-color: #EEEEEE;
}
td {
    text-align: right;
}
td:first-child, th:first-child {
    text-align: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div style="width:3000px">some really really wide content goes here</div>
<table>
    <thead>
        <tr>
            <th colspan="9">Companies listed on NASDAQ OMX Copenhagen.</th>
        </tr>
        <tr>
            <th>Full name</th>
            <th>CCY</th>
            <th>Last</th>
            <th>+/-</th>
            <th>%</th>
            <th>Bid</th>
            <th>Ask</th>
            <th>Volume</th>
            <th>Turnover</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>A.P. Møller...</td>
            <td>DKK</td>
            <td>33,220.00</td>
            <td>760</td>
            <td>2.34</td>
            <td>33,140.00</td>
            <td>33,220.00</td>
            <td>594</td>
            <td>19,791,910</td>
        </tr>
        <tr>
            <td>A.P. Møller...</td>
            <td>DKK</td>
            <td>34,620.00</td>
            <td>640</td>
            <td>1.88</td>
            <td>34,620.00</td>
            <td>34,700.00</td>
            <td>9,954</td>
            <td>346,530,246</td>
        </tr>
        <tr>
            <td>Carlsberg A</td>
            <td>DKK</td>
            <td>380</td>
            <td>0</td>
            <td>0</td>
            <td>371</td>
            <td>391.5</td>
            <td>6</td>
            <td>2,280</td>
        </tr>
        <tr>
            <td>Carlsberg B</td>
            <td>DKK</td>
            <td>364.4</td>
            <td>8.6</td>
            <td>2.42</td>
            <td>363</td>
            <td>364.4</td>
            <td>636,267</td>
            <td>228,530,601</td>
        </tr>
        <tr>
            <td>Chr. Hansen...</td>
            <td>DKK</td>
            <td>114.5</td>
            <td>-1.6</td>
            <td>-1.38</td>
            <td>114.2</td>
            <td>114.5</td>
            <td>141,822</td>
            <td>16,311,454</td>
        </tr>
        <tr>
            <td>Coloplast B</td>
            <td>DKK</td>
            <td>809.5</td>
            <td>11</td>
            <td>1.38</td>
            <td>809</td>
            <td>809.5</td>
            <td>85,840</td>
            <td>69,363,301</td>
        </tr>
        <tr>
            <td>D/S Norden</td>
            <td>DKK</td>
            <td>155</td>
            <td>-1.5</td>
            <td>-0.96</td>
            <td>155</td>
            <td>155.1</td>
            <td>51,681</td>
            <td>8,037,225</td>
        </tr>
        <tr>
            <td>Danske Bank</td>
            <td>DKK</td>
            <td>69.05</td>
            <td>2.55</td>
            <td>3.83</td>
            <td>69.05</td>
            <td>69.2</td>
            <td>1,723,719</td>
            <td>115,348,068</td>
        </tr>
        <tr>
            <td>DSV</td>
            <td>DKK</td>
            <td>105.4</td>
            <td>0.2</td>
            <td>0.19</td>
            <td>105.2</td>
            <td>105.4</td>
            <td>674,873</td>
            <td>71,575,035</td>
        </tr>
        <tr>
            <td>FLSmidth &amp; Co.</td>
            <td>DKK</td>
            <td>295.8</td>
            <td>-1.8</td>
            <td>-0.6</td>
            <td>295.1</td>
            <td>295.8</td>
            <td>341,263</td>
            <td>100,301,032</td>
        </tr>
        <tr>
            <td>G4S plc</td>
            <td>DKK</td>
            <td>22.53</td>
            <td>0.05</td>
            <td>0.22</td>
            <td>22.53</td>
            <td>22.57</td>
            <td>190,920</td>
            <td>4,338,150</td>
        </tr>
        <tr>
            <td>Jyske Bank</td>
            <td>DKK</td>
            <td>144.2</td>
            <td>1.4</td>
            <td>0.98</td>
            <td>142.8</td>
            <td>144.2</td>
            <td>78,163</td>
            <td>11,104,874</td>
        </tr>
        <tr>
            <td>Københavns ...</td>
            <td>DKK</td>
            <td>1,580.00</td>
            <td>-12</td>
            <td>-0.75</td>
            <td>1,590.00</td>
            <td>1,620.00</td>
            <td>82</td>
            <td>131,110</td>
        </tr>
        <tr>
            <td>Lundbeck</td>
            <td>DKK</td>
            <td>103.4</td>
            <td>-2.5</td>
            <td>-2.36</td>
            <td>103.4</td>
            <td>103.8</td>
            <td>157,162</td>
            <td>16,462,282</td>
        </tr>
        <tr>
            <td>Nordea Bank</td>
            <td>DKK</td>
            <td>43.22</td>
            <td>-0.06</td>
            <td>-0.14</td>
            <td>43.22</td>
            <td>43.25</td>
            <td>167,520</td>
            <td>7,310,143</td>
        </tr>
        <tr>
            <td>Novo Nordisk B</td>
            <td>DKK</td>
            <td>552.5</td>
            <td>-3.5</td>
            <td>-0.63</td>
            <td>550.5</td>
            <td>552.5</td>
            <td>843,533</td>
            <td>463,962,375</td>
        </tr>
        <tr>
            <td>Novozymes B</td>
            <td>DKK</td>
            <td>805.5</td>
            <td>5.5</td>
            <td>0.69</td>
            <td>805</td>
            <td>805.5</td>
            <td>152,188</td>
            <td>121,746,199</td>
        </tr>
        <tr>
            <td>Pandora</td>
            <td>DKK</td>
            <td>39.04</td>
            <td>0.94</td>
            <td>2.47</td>
            <td>38.8</td>
            <td>39.04</td>
            <td>350,965</td>
            <td>13,611,838</td>
        </tr>
        <tr>
            <td>Rockwool In...</td>
            <td>DKK</td>
            <td>492</td>
            <td>0</td>
            <td>0</td>
            <td>482</td>
            <td>492</td>
            <td></td>
            <td></td>
        </tr>
        <tr>
            <td>Rockwool In...</td>
            <td>DKK</td>
            <td>468</td>
            <td>12</td>
            <td>2.63</td>
            <td>465.2</td>
            <td>468</td>
            <td>9,885</td>
            <td>4,623,850</td>
        </tr>
        <tr>
            <td>Sydbank</td>
            <td>DKK</td>
            <td>95</td>
            <td>0.05</td>
            <td>0.05</td>
            <td>94.7</td>
            <td>95</td>
            <td>103,438</td>
            <td>9,802,899</td>
        </tr>
        <tr>
            <td>TDC</td>
            <td>DKK</td>
            <td>43.6</td>
            <td>0.13</td>
            <td>0.3</td>
            <td>43.5</td>
            <td>43.6</td>
            <td>845,110</td>
            <td>36,785,339</td>
        </tr>
        <tr>
            <td>Topdanmark</td>
            <td>DKK</td>
            <td>854</td>
            <td>13.5</td>
            <td>1.61</td>
            <td>854</td>
            <td>855</td>
            <td>38,679</td>
            <td>32,737,678</td>
        </tr>
        <tr>
            <td>Tryg</td>
            <td>DKK</td>
            <td>290.4</td>
            <td>0.3</td>
            <td>0.1</td>
            <td>290</td>
            <td>290.4</td>
            <td>94,587</td>
            <td>27,537,247</td>
        </tr>
        <tr>
            <td>Vestas Wind...</td>
            <td>DKK</td>
            <td>90.15</td>
            <td>-4.2</td>
            <td>-4.45</td>
            <td>90.1</td>
            <td>90.15</td>
            <td>1,317,313</td>
            <td>121,064,314</td>
        </tr>
        <tr>
            <td>William Dem...</td>
            <td>DKK</td>
            <td>417.6</td>
            <td>0.1</td>
            <td>0.02</td>
            <td>417</td>
            <td>417.6</td>
            <td>64,242</td>
            <td>26,859,554</td>
        </tr>
    </tbody>
</table>
<div style="height: 4000px">lots of content down here...</div>

вот улучшенный ответ на сообщение от Максимилиан Хилы.

это работает в IE11 без мерцания вообще:

var headerCells = tableWrap.querySelectorAll("thead td");
for (var i = 0; i < headerCells.length; i++) {
    var headerCell = headerCells[i];
    headerCell.style.backgroundColor = "silver";
}
var lastSTop = tableWrap.scrollTop;
tableWrap.addEventListener("scroll", function () {
    var stop = this.scrollTop;
    if (stop < lastSTop) {
        // reseting the transform for the scrolling up to hide the headers
        for (var i = 0; i < headerCells.length; i++) {
            headerCells[i].style.transitionDelay = "0s";
            headerCells[i].style.transform = "";
        }
    }
    lastSTop = stop;
    var translate = "translate(0," + stop + "px)";
    for (var i = 0; i < headerCells.length; i++) {
        headerCells[i].style.transitionDelay = "0.25s";
        headerCells[i].style.transform = translate;
    }
});

мне нравится Максимилиан Хилс ответ, но у меня были некоторые проблемы:

  1. преобразование не работает в Edge или IE, если вы не примените его к th
  2. заголовок мерцает во время прокрутки в Edge и IE
  3. моя таблица загружается с помощью ajax, поэтому я хотел прикрепить к событию прокрутки окна, а не к событию прокрутки оболочки

чтобы избавиться от мерцания, я использую тайм-аут, чтобы дождаться, пока пользователь закончит прокрутка, затем я применяю преобразование - так что заголовок не отображается во время прокрутки.

Я также написал это с помощью jQuery, одним из преимуществ этого является то, что jQuery должен обрабатывать префиксы поставщиков для вас

    var isScrolling, lastTop, lastLeft, isLeftHidden, isTopHidden;

    //Scroll events don't bubble https://stackoverflow.com/a/19375645/150342
    //so can't use $(document).on("scroll", ".table-container-fixed", function (e) {
    document.addEventListener('scroll', function (event) {
        var $container = $(event.target);
        if (!$container.hasClass("table-container-fixed"))
            return;    

        //transform needs to be applied to th for Edge and IE
        //in this example I am also fixing the leftmost column
        var $topLeftCell = $container.find('table:first > thead > tr > th:first');
        var $headerCells = $topLeftCell.siblings();
        var $columnCells = $container
           .find('table:first > tbody > tr > td:first-child, ' +
                 'table:first > tfoot > tr > td:first-child');

        //hide the cells while returning otherwise they show on top of the data
        if (!isLeftHidden) {
            var currentLeft = $container.scrollLeft();
            if (currentLeft < lastLeft) {
                //scrolling left
                isLeftHidden = true;
                $topLeftCell.css('visibility', 'hidden');
                $columnCells.css('visibility', 'hidden');
            }
            lastLeft = currentLeft;
        }

        if (!isTopHidden) {
            var currentTop = $container.scrollTop();
            if (currentTop < lastTop) {
                //scrolling up
                isTopHidden = true;
                $topLeftCell.css('visibility', 'hidden');
                $headerCells.css('visibility', 'hidden');
            }
            lastTop = currentTop;
        }

        // Using timeout to delay transform until user stops scrolling
        // Clear timeout while scrolling
        window.clearTimeout(isScrolling);

        // Set a timeout to run after scrolling ends
        isScrolling = setTimeout(function () {
            //move the table cells. 
            var x = $container.scrollLeft();
            var y = $container.scrollTop();

            $topLeftCell.css('transform', 'translate(' + x + 'px, ' + y + 'px)');
            $headerCells.css('transform', 'translateY(' + y + 'px)');
            $columnCells.css('transform', 'translateX(' + x + 'px)');

            isTopHidden = isLeftHidden = false;
            $topLeftCell.css('visibility', 'inherit');
            $headerCells.css('visibility', 'inherit');
            $columnCells.css('visibility', 'inherit');
        }, 100);

    }, true);

таблица обернута в div с классом table-container-fixed.

.table-container-fixed{
    overflow: auto;
    height: 400px;
}

Я устанавливаю border-collapse для разделения, потому что в противном случае мы теряем границы во время перевода, и я удаляю границу на таблице, чтобы остановить контент, появляющийся чуть выше клетки, где граница была во время прокрутки.

.table-container-fixed > table {
   border-collapse: separate;
   border:none;
}

Я th фон белый, чтобы покрыть ячейки под ним, и я добавляю границу, которая соответствует границе таблицы, которая стилизована с помощью Bootstrap и прокручивается вне поля зрения.

 .table-container-fixed > table > thead > tr > th {
        border-top: 1px solid #ddd !important;
        background-color: white;        
        z-index: 10;
        position: relative;/*to make z-index work*/
    }

            .table-container-fixed > table > thead > tr > th:first-child {
                z-index: 20;
            }

.table-container-fixed > table > tbody > tr > td:first-child,
.table-container-fixed > table > tfoot > tr > td:first-child {
    background-color: white;        
    z-index: 10;
    position: relative;
}

каким-то образом я оказался Position:Sticky отлично работает в моем случае

table{
  width:100%;
  border:collapse;
  
}
th{
  position: sticky;
    top: 0px;
    border:1px solid black;
    background : #ff5722;
    color:#f5f5f5;
    font-weight:600;
}
td{
    background:#d3d3d3;
    border:1px solid black;
    color:#f5f5f5;
    font-weight:600;
}

div{
  height:150px
  overflow:auto;
  width:100%
}
<div>


<table>
<thead>
<tr>
<th>header 1 </th>
<th>header 2 </th>
<th>header 3 </th>
<th>header 4 </th>
<th>header 5 </th>
<th>header 6 </th>
<th>header 7 </th>
</tr>
</thead>
<tbody>
  <tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>
<tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>
<tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>  
  <tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>  
  <tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>  
  <tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>  
  <tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>  
  <tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>  
  <tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>  
  <tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>  
  <tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>  
  <tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>  
  <tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>  
  <tr>
    <td>data 1</td>
    <td>data 2</td>
    <td>data 3</td>
    <td>data 4</td>
    <td>data 5</td>
    <td>data 6</td>
    <td>data 7</td>    
  </tr>  
  
</tbody>
</table>

</div>