如何使用JavaScript和CSS来滚动时锁定表格的第一行和第一列?

36
如何创建一个表格,使其第一行和第一列都被锁定,就像在Excel中激活“冻结窗格”时一样?我需要该表格能够在水平和垂直方向上滚动(有许多解决方案可供选择,但仅允许垂直滚动)。
因此,当您在表格中向下滚动时,第一行将保持不变,因为它将具有列标题。这可能最终会出现在中,也可能不是这样,具体取决于哪个方案更容易实现。
当您向右滚动时,第一列将保持不变,因为它包含行的标签。
我相信仅使用CSS无法实现此目标,但有人能否指向一个JavaScript解决方案?它需要在所有主要浏览器中运行。

你好,我知道这个问题有点老了,但是你找到解决方案了吗?现在标记为正确答案的回答链接已经失效了。我也在尝试寻找同样的解决方法:https://dev59.com/73RB5IYBdhLWcg3wCjYV - Damovisa
我刚刚尝试了被接受的答案中的链接,它们对我有效。你还有问题吗? - pkaeding
13个回答

12

好的,我搜索了可滚动列固定的表格以了解这个具体需求,并且回答问题,但没有找到合适的答案。

我回答了这个问题 Large dynamically sized html table with a fixed scroll row and fixed scroll column ,这启发我展示我的作品,即一个插件 https://github.com/meetselva/fixed-table-rows-cols

该插件可以将格式良好的HTML表格转换为带有固定表头和列的可滚动表格。

使用方法如下,

$('#myTable').fxdHdrCol({
    fixedCols    : 3,       /* 3 fixed columns */
    width        : "100%",  /* set the width of the container (fixed or percentage)*/
    height       : 500      /* set the height of the container */
});

您可以在这里查看演示和文档


这是一个不错的插件。有没有可能有一个 Angular 版本? - kane
我看了你的好作品,有一个问题想问一下,你提供给colModal数组的所有内容是否都是基于固定列宽的前提呢?我有一个表格,其中包含可变列宽,但我似乎无法弄清如何使用你的代码来处理这种情况。我想知道这是否是对colModalwidth属性width number Set the initial width of the column, in pixels. This value currently can not be set as percentage的级联依赖。 - jxramos

7

我是这样做的:

  • 使用多个表格
  • 固定大小的单元格
  • 使用jQuery的scrollTop和scrollLeft函数

这里有一个jsfiddle示例来演示。

尚未在所有浏览器上测试,但我想在旧版IE上可能不太好。

$("#clscroll-content").scroll(function() {
    $("#clscroll-row-headers").scrollTop($("#clscroll-content").scrollTop());
    $("#clscroll-column-headers").scrollLeft($("#clscroll-content").scrollLeft());
});

$("#clscroll-column-headers").scroll(function() {
    $("#clscroll-content").scrollLeft($("#clscroll-column-headers").scrollLeft());
});

$("#clscroll-row-headers").scroll(function() {
    $("#clscroll-content").scrollTop($("#clscroll-row-headers").scrollTop());
});
.clscroll table {
    table-layout: fixed;
}

.clscroll td, .clscroll th { 
    overflow: hidden;
}

.corner-header {
    float: left;
}

.column-headers {
    float: left;
    overflow: scroll;
}

.row-headers {
    clear: both;
    float: left;    
    overflow: scroll;
}

.table-content {
    table-layout: fixed;
    float: left;
    overflow: scroll;
}

.clscroll td, .clscroll th { 
    width: 200px;
    border: 1px solid black;
}

.row-headers, .table-content {
    height: 100px;
}

.column-headers, .table-content, .table-content table, .column-headers table {
    width: 400px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="clscroll corner-header">
  <table>
      <tr>
          <th>&nbsp;</th>
      </tr>
  </table>
</div>
<div class="clscroll column-headers" id="clscroll-column-headers">
  <table>
      <tr>
          <th>Bus</th>
          <th>Plane</th>
          <th>Boat</th>
          <th>Bicycle</th>
      </tr>
  </table>
</div>
<div class="clscroll row-headers" id="clscroll-row-headers">
  <table>
      <tr>
          <th>Red</th>
      </tr>
      <tr>
          <th>Green</th>
      </tr>
      <tr>
          <th>Blue</th>
      </tr>
      <tr>
          <th>Orange</th>
      </tr>
      <tr>
          <th>Purple</th>
      </tr>
      <tr>
          <th>Yellow</th>
      </tr>
      <tr>
          <th>Pink</th>
      </tr>
      <tr>
          <th>Brown</th>
      </tr>
  </table>
</div>
<div class="clscroll table-content" id="clscroll-content">
  <table>
      <tr>
          <td>Red Bus</td>
          <td>Red Plane</td>
          <td>Red Boat</td>
          <td>Red Bicycle</td>
      </tr>
      <tr>
          <td>Green Bus</td>
          <td>Green Plane</td>
          <td>Green Boat</td>
          <td>Green Bicycle</td>
      </tr>
      <tr>
          <td>Blue Bus</td>
          <td>Blue Plane</td>
          <td>Blue Boat</td>
          <td>Blue Bicycle</td>
      </tr>
      <tr>
          <td>Orange Bus</td>
          <td>Orange Plane</td>
          <td>Orange Boat</td>
          <td>Orange Bicycle</td>
      </tr>
      <tr>
          <td>Purple Bus</td>
          <td>Purple Plane</td>
          <td>Purple Boat</td>
          <td>Purple Bicycle</td>
      </tr>
      <tr>
          <td>Yellow Bus</td>
          <td>Yellow Plane</td>
          <td>Yellow Boat</td>
          <td>Yellow Bicycle</td>
      </tr>
      <tr>
          <td>Pink Bus</td>
          <td>Pink Plane</td>
          <td>Pink Boat</td>
          <td>Pink Bicycle</td>
      </tr>
      <tr>
          <td>Brown Bus</td>
          <td>Brown Plane</td>
          <td>Brown Boat</td>
          <td>Brown Bicycle</td>
      </tr>
  </table>
</div>


非常好,谢谢。我派生出来只有两个滚动条的版本。http://jsfiddle.net/2sc64/ - manyways

4

这似乎不支持锁定第一列。或者我有什么遗漏吗?如果没有,那么这个解决方案就不比我在问题中提到的“常规”解决方案更好。 - pkaeding
我已经更新了我的演示页面,并添加了锁定第一列的演示。 - Mark

4

您需要两个表格,其中第一个表格与第二个表格完全重叠。第二个表格包含所有数据,而第一个表格只包含第一列。您需要同步它的宽度和每行的高度(根据内容)。

除了这两个表格之外,您还需要第三个表格。这是第一行,恰好位于其他两个表格之间,并且必须以相同的方式进行同步。

在这里需要绝对定位。接下来,您需要将数据表的滚动与头部行和第一列表格的滚动位置同步。

这在所有主要浏览器中都非常有效,但有一个问题:同步滚动会出现抖动。为解决这个问题,您需要两个外部 div 容器,它们分别包含头部行和第一列内容的克隆。在垂直滚动时,您显示头部行的克隆以防止抖动,同时将原始克隆到后台中。在水平滚动时,您将显示第一行的克隆。同样的方法也适用于此。


这比我最初想要做的要复杂得多,但最终,这就是我做的(或多或少)。看起来这应该比它现在的难度要容易得多... - pkaeding
我同意这看起来过于复杂,但我找不到其他解决方案。 - niczak

2
今天有相当多的跨浏览器解决方案,其中包括 SuperTable ,我喜欢它的优雅和简单(现在已经继续开发为 MooGrid),还有 SlickGrid 具备其强大的功能集。

1

你需要进行测试,但如果你在页面中嵌入一个iframe,然后使用CSS将第一行和第一列绝对定位于iframe页面的0,0位置,这样能解决你的问题吗?


除非我误解了您的意思,否则我认为这样做不会使标题在数据滚动时更新(即,当数据向下滚动时,将行标题向下滚动,以便行标题仍对应相应的行)。 - pkaeding

1

这里是一个JQuery插件:https://github.com/nitsugario/jQuery-Freeze-Table-Column-and-Rows

这是一个jQuery插件,它可以使表格行和列不滚动。它可以获取给定的HTML表格对象,并设置它,以便可以冻结给定数量的列或行或两者同时,使固定的列或行不滚动。应该将要冻结的行放置在表头部分。还可以使用colspan或rowspan属性组合冻结行和列。


0
怎么样使用一个解决方案,在表格的实际“数据”放置在自己的
内,然后使用overflow: scroll;?这样浏览器将自动为你不想锁定的“表格”部分创建滚动条,而你可以将“表头”/第一行刚好放在那个
的上面。
不确定这如何在水平滚动时工作。

是的,这适用于垂直滚动,但不适用于水平滚动。那就是我在问题中提到的常见解决方案。 - pkaeding

0

排序并锁定表格是我见过的唯一可以在IE以外的浏览器上正常工作的解决方案。(虽然这个“锁定列CSS”也可能有用)。下面是所需的代码块。

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <meta name="robots" content="noindex, nofollow">
  <meta name="googlebot" content="noindex, nofollow">
  <script type="text/javascript" src="/js/lib/dummy.js"></script>
    <link rel="stylesheet" type="text/css" href="/css/result-light.css">
  <style type="text/css">
    /* Scrollable Content Height */
.scrollContent {
 height:100px;
 overflow-x:hidden;
 overflow-y:auto;
}
.scrollContent tr {
 height: auto;
 white-space: nowrap;
}

/* Prevent Mozilla scrollbar from hiding right-most cell content */
.scrollContent tr td:last-child {
 padding-right: 20px;
}

/* Fixed Header Height */
.fixedHeader tr {
 position: relative;
 height: auto;
}

/* Put border around entire table */
div.TableContainer {
 border: 1px solid #7DA87D;
}

/* Table Header formatting */
.headerFormat {
 background-color: white;
 color: #FFFFFF;
 margin: 3px;
 padding: 1px;
 white-space: nowrap;
 font-family: Helvetica;
 font-size: 16px;
 text-decoration: none;
 font-weight: bold;
}
.headerFormat tr td {
 border: 1px solid #000000;
 background-color: #7DA87D;
}

/* Table Body (Scrollable Content) formatting */
.bodyFormat tr td {
    color: #000000;
    margin: 3px;
    padding: 1px;
    border: 0px none;
    font-family: Helvetica;
    font-size: 12px;
}

/* Use to set different color for alternating rows */
.alternateRow {
  background-color: #E0F1E0;
}

/* Styles used for SORTING */
.point {
 cursor:pointer;
}
td.sortedColumn {
  background-color: #E0F1E0;
}

tr.alternateRow td.sortedColumn {
  background-color: #c5e5c5;
}
.total {
    background-color: #FED362;
    color: #000000;
    white-space: nowrap;
    font-size: 12px;
    text-decoration: none;
}
  </style>

  <title></title>
<script type='text/javascript'>//<![CDATA[

/* This script and many more are available free online at
The JavaScript Source :: http://www.javascriptsource.com
Created by: Stan Slaughter :: http://www.stansight.com/ */

/* ======================================================
Generic Table Sort

Basic Concept: A table can be sorted by clicking on the title of any
column in the table, toggling between ascending and descending sorts.


Assumptions:

* The first row of the table contains column titles that are "clicked"
  to sort the table

* The images 'desc.gif','asc.gif','none.gif','sorting.gif' exist

* The img tag is in each column of the the title row to represent the
  sort graphic.

* The CSS classes 'alternateRow' and 'sortedColumn' exist so we can
  have alternating colors for each row and a highlight the sorted
  column.  Something like the <style> definition below, but with the
  background colors set to whatever you want.

   <style>
   tr.alternateRow {
     background-color: #E0F1E0;
   }

   td.sortedColumn {
     background-color: #E0F1E0;
   }

   tr.alternateRow td.sortedColumn {
     background-color: #c5e5c5;
   }
   </style>

====================================================== */

function sortTable(td_element,ignoreLastLines) {

  // If the optional ignoreLastLines parameter (number of lines *not* to sort at end of table)
  // was not passed then make it 0
  ignoreLastLines = (typeof(ignoreLastLines)=='undefined') ? 0 : ignoreLastLines;

  var sortImages =['data:image/gif;base64,R0lGODlhCgAKAMQXAJOkk3mReXume3uTe3mieXGPcXOYc/Hx8Xadds/Wz9vg24ejh3GUcYOgg6a0pnGVcfP18+3w7c3TzdPY06u4q/r8+v///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABcALAAAAAAKAAoAAAUz4IVcZDleixQIQjA1pFFZx2FVRklZvOWUl8LsVgBeFLyE8TLgDZYESISwvAAA1QvjAQwBADs=','data:image/gif;base64,R0lGODlhCgAKAMQXAJOkk3mReXume3uTe3mieXGPcXOYc/Hx8Xadds/Wz9vg24ejh3GUcYOgg6a0pnGVcfP18+3w7c3TzdPY06u4q/r8+v///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABcALAAAAAAKAAoAAAUw4CVeDzOeFwCgIhFBBDtY1sAmtIIWFV0VJweNRhkZeoeDpWIQNSYBgSAgWYgQLGwIADs=','data:image/gif;base64,R0lGODlhCgAKALMLAHaRdnCTcHegd7C8sNTa1Ku4q9vg24GXgfr8+uDl4P///////wAAAAAAAAAAAAAAACH5BAEAAAsALAAAAAAKAAoAAAQfcMlJq12hIHKoSEqIdBIQnslknkoqfedIBQNikFduRQA7','http://web.archive.org/web/20150906203819im_/http://www.javascriptsource.com/miscellaneous/sorting.gif'];

  // Get the image used in the first row of the current column
  var sortColImage = td_element.getElementsByTagName('img')[0];

  // If current image is 'asc.gif' or 'none.gif' (elements 1 and 2 of sortImages array) then this will
  // be a descending sort else it will be ascending - get new sort image icon and set sort order flag
  var sortAscending = false;
  var newSortColImage = "";
  if (sortColImage.getAttribute('src').indexOf(sortImages[1])>-1 ||
    sortColImage.getAttribute('src').indexOf(sortImages[2])>-1) {
    newSortColImage = sortImages[0];
    sortAscending = false;
  } else {
    newSortColImage = sortImages[1];
    sortAscending = true;
  }

  // Assign "SORTING" image icon (element 3 of sortImages array)) to current column title
  // (will replace with newSortColImage when sort completes)
  sortColImage.setAttribute('src',sortImages[3]);

  // Find which column was clicked by getting it's column position
  var indexCol = td_element.cellIndex;

  // Get the table element from the td element that was passed as a parameter to this function
  var table_element = td_element.parentNode;
  while (table_element.nodeName != "TABLE") {
    table_element = table_element.parentNode;
  }

  // Get all "tr" elements from the table and assign then to the Array "tr_elements"
  var tr_elements = table_element.getElementsByTagName('tr');

  // Get all the images used in the first row then set them to 'none.gif'
  // (element 2 or sortImages array) except for the current column (all ready been changed)
  var allImg = tr_elements[0].getElementsByTagName('img');
    for(var i=0;i<allImg.length;i++){
    if(allImg[i]!=sortColImage){allImg[i].setAttribute('src',sortImages[2])}
  }

  // Some explantion of the basic concept of the following code before we
  // actually start.  Essentially we are going to copy the current columns information
  // into an array to be sorted. We'll sort the column array then go back and use the information
  // we saved about the original row positions to re-order the entire table.
  // We are never really sorting more than a columns worth of data, which should keep the sorting fast.

  // Create a new array for holding row information
  var clonedRows = new Array()

  // Create a new array to store just the selected column values, not the whole row
  var originalCol = new Array();

  // Now loop through all the data row elements
  // NOTE: Starting at row 1 because row 0 contains the column titles
  for (var i=1; i<tr_elements.length - ignoreLastLines; i++) {

   // "Clone" the tr element i.e. save a copy all of its attributes and values
   clonedRows[i]=tr_elements[i].cloneNode(true);

   // Text value of the selected column on this row
   var valueCol = getTextValue(tr_elements[i].cells[indexCol]);

   // Format text value for sorting depending on its type, ie Date, Currency, number, etc..
   valueCol = FormatForType(valueCol);

   // Assign the column value AND the row number it was originally on in the table
   originalCol[i]=[valueCol,tr_elements[i].rowIndex];
  }

  // Get rid of element "0" from this array.  A value was never assigned to it because the first row
  // in the table just contained the column titles, which we did not bother to assign.
  originalCol.shift();

  // Sort the column array returning the value of a sort into a new array
  sortCol = originalCol.sort(sortCompare);

  // If it was supposed to be an Ascending sort then reverse the order
  if (sortAscending) { sortCol.reverse(); }

  // Now take the values from the sorted column array and use that information to re-arrange
  // the order of the tr_elements in the table
  for (var i=1; i < tr_elements.length - ignoreLastLines; i++) {

    var old_row = sortCol[i-1][1];
    var new_row = i;
    tr_elements[i].parentNode.replaceChild(clonedRows[old_row],tr_elements[new_row]);
  }

   // Format the table, making the rows alternating colors and highlight the sorted column
   makePretty(table_element,indexCol,ignoreLastLines);

  // Assign correct sort image icon to current column title
  sortColImage.setAttribute('src',newSortColImage);
}

// Function used by the sort routine to compare the current value in the array with the next one
function sortCompare (currValue, nextValue) {
 // Since the elements of this array are actually arrays themselves, just sort
 // on the first element which contiains the value, not the second which contains
 // the original row position
  if ( currValue[0] == nextValue[0] ) return 0;
  if ( currValue[0] < nextValue[0] ) return -1;
  if ( currValue[0] > nextValue[0] ) return 1;
}

//-----------------------------------------------------------------------------
// Functions to get and compare values during a sort.
//-----------------------------------------------------------------------------

// This code is necessary for browsers that don't reflect the DOM constants
// (like IE).
if (document.ELEMENT_NODE == null) {
   document.ELEMENT_NODE = 1;
   document.TEXT_NODE = 3;
}

function getTextValue(el) {
  var i;
  var s;
  // Find and concatenate the values of all text nodes contained within the
  // element.
  s = "";
  for (i = 0; i < el.childNodes.length; i++)
    if (el.childNodes[i].nodeType == document.TEXT_NODE)
      s += el.childNodes[i].nodeValue;
    else if (el.childNodes[i].nodeType == document.ELEMENT_NODE &&
             el.childNodes[i].tagName == "BR")
      s += " ";
    else
      // Use recursion to get text within sub-elements.
      s += getTextValue(el.childNodes[i]);

  return normalizeString(s);
}

// Regular expressions for normalizing white space.
var whtSpEnds = new RegExp("^\\s*|\\s*$", "g");
var whtSpMult = new RegExp("\\s\\s+", "g");

function normalizeString(s) {
  s = s.replace(whtSpMult, " ");  // Collapse any multiple whites space.
  s = s.replace(whtSpEnds, "");   // Remove leading or trailing white space.
  return s;
}

// Function used to modify values to make then sortable depending on the type of information
function FormatForType(itm) {
  var sortValue = itm.toLowerCase();

  // If the item matches a date pattern (MM/DD/YYYY or MM/DD/YY or M/DD/YYYY)
  if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/) ||
      itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/) ||
      itm.match(/^\d[\/-]\d\d[\/-]\d\d\d\d$/) ) {

    // Convert date to YYYYMMDD format for sort comparison purposes
    // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
    var yr = -1;

    if (itm.length == 10) {
      sortValue = itm.substr(6,4)+itm.substr(0,2)+itm.substr(3,2);
     } else if (itm.length == 9) {
      sortValue = itm.substr(5,4)+"0" + itm.substr(0,1)+itm.substr(2,2);
    } else {
      yr = itm.substr(6,2);
      if (parseInt(yr) < 50) {
        yr = '20'+yr;
      } else {
        yr = '19'+yr;
      }
        sortValue = yr+itm.substr(3,2)+itm.substr(0,2);
    }

  }



  // If the item matches a Percent patten (contains a percent sign)
  if (itm.match(/%/)) {
   // Replace anything that is not part of a number (decimal pt, neg sign, or 0 through 9) with an empty string.
   sortValue = itm.replace(/[^0-9.-]/g,'');
   sortValue = parseFloat(sortValue);
  }

  // If item starts with a "(" and ends with a ")" then remove them and put a negative sign in front
  if (itm.substr(0,1) == "(" & itm.substr(itm.length - 1,1) == ")") {
   itm = "-" + itm.substr(1,itm.length - 2);
  }

// If the item matches a currency pattern (starts with a dollar or negative dollar sign)
  if (itm.match(/^[£$]|(^-)/)) {
   // Replace anything that is not part of a number (decimal pt, neg sign, or 0 through 9) with an empty string.
   sortValue = itm.replace(/[^0-9.-]/g,'');
   if (isNaN(sortValue)) {
     sortValue = 0;
   } else {
     sortValue = parseFloat(sortValue);
   }
}

  // If the item matches a numeric pattern
  if (itm.match(/(\d*,\d*$)|(^-?\d\d*\.\d*$)|(^-?\d\d*$)|(^-?\.\d\d*$)/)) {
   // Replace anything that is not part of a number (decimal pt, neg sign, or 0 through 9) with an empty string.
   sortValue = itm.replace(/[^0-9.-]/g,'');
 //  sortValue = sortValue.replace(/,/g,'');
   if (isNaN(sortValue)) {
     sortValue = 0;
   } else {
     sortValue = parseFloat(sortValue);
   }
  }

  return sortValue;
}

//-----------------------------------------------------------------------------
// Functions to update the table appearance after a sort.
//-----------------------------------------------------------------------------

// Style class names.
var rowClsNm = "alternateRow";
var colClsNm = "sortedColumn";

// Regular expressions for setting class names.
var rowTest = new RegExp(rowClsNm, "gi");
var colTest = new RegExp(colClsNm, "gi");

function makePretty(tblEl, col, ignoreLastLines) {

  var i, j;
  var rowEl, cellEl;

  // Set style classes on each row to alternate their appearance.
  for (i = 1; i < tblEl.rows.length - ignoreLastLines; i++) {
   rowEl = tblEl.rows[i];
   rowEl.className = rowEl.className.replace(rowTest, "");
    if (i % 2 != 0)
      rowEl.className += " " + rowClsNm;
    rowEl.className = normalizeString(rowEl.className);
    // Set style classes on each column (other than the name column) to
    // highlight the one that was sorted.
    for (j = 0; j < tblEl.rows[i].cells.length; j++) {
      cellEl = rowEl.cells[j];
      cellEl.className = cellEl.className.replace(colTest, "");
      if (j == col)
        cellEl.className += " " + colClsNm;
      cellEl.className = normalizeString(cellEl.className);
    }
  }


}

// END Generic Table sort.

// =================================================

// Function to scroll to top before sorting to fix an IE bug
// Which repositions the header off the top of the screen
// if you try to sort while scrolled to bottom.
function GoTop() {
 document.getElementById('TableContainer').scrollTop = 0;
}

//]]> 
</script>
</head>
<body>
  <table cellpadding="0" cellspacing="0" border="0">
<tr><td>
<div id="TableContainer" class="TableContainer" style="height:230px;">
<table class="scrollTable">
 <thead class="fixedHeader headerFormat">
  <tr>
   <td class="point" onclick="GoTop(); sortTable(this,1);" title="Sort"><b>NAME</b> <img src="data:image/gif;base64,R0lGODlhCgAKALMLAHaRdnCTcHegd7C8sNTa1Ku4q9vg24GXgfr8+uDl4P///////wAAAAAAAAAAAAAAACH5BAEAAAsALAAAAAAKAAoAAAQfcMlJq12hIHKoSEqIdBIQnslknkoqfedIBQNikFduRQA7" border="0"></td>
   <td class="point" onclick="GoTop(); sortTable(this,1);" title="Sort" align="right"><b>Amt</b> <img src="data:image/gif;base64,R0lGODlhCgAKALMLAHaRdnCTcHegd7C8sNTa1Ku4q9vg24GXgfr8+uDl4P///////wAAAAAAAAAAAAAAACH5BAEAAAsALAAAAAAKAAoAAAQfcMlJq12hIHKoSEqIdBIQnslknkoqfedIBQNikFduRQA7" border="0"></td>
   <td class="point" onclick="GoTop(); sortTable(this,1);" title="Sort" align="right"><b>Lvl</b> <img src="data:image/gif;base64,R0lGODlhCgAKALMLAHaRdnCTcHegd7C8sNTa1Ku4q9vg24GXgfr8+uDl4P///////wAAAAAAAAAAAAAAACH5BAEAAAsALAAAAAAKAAoAAAQfcMlJq12hIHKoSEqIdBIQnslknkoqfedIBQNikFduRQA7" border="0"></td>
   <td class="point" onclick="GoTop(); sortTable(this,1);" title="Sort" align="right"><b>Rank</b> <img src="data:image/gif;base64,R0lGODlhCgAKALMLAHaRdnCTcHegd7C8sNTa1Ku4q9vg24GXgfr8+uDl4P///////wAAAAAAAAAAAAAAACH5BAEAAAsALAAAAAAKAAoAAAQfcMlJq12hIHKoSEqIdBIQnslknkoqfedIBQNikFduRQA7" border="0"></td>
   <td class="point" onclick="GoTop(); sortTable(this,1);" title="Sort" align="right"><b>Position</b> <img src="data:image/gif;base64,R0lGODlhCgAKALMLAHaRdnCTcHegd7C8sNTa1Ku4q9vg24GXgfr8+uDl4P///////wAAAAAAAAAAAAAAACH5BAEAAAsALAAAAAAKAAoAAAQfcMlJq12hIHKoSEqIdBIQnslknkoqfedIBQNikFduRQA7" border="0"></td>
   <td class="point" onclick="GoTop(); sortTable(this,1);" title="Sort" align="right"><b>Date</b> <img src="data:image/gif;base64,R0lGODlhCgAKALMLAHaRdnCTcHegd7C8sNTa1Ku4q9vg24GXgfr8+uDl4P///////wAAAAAAAAAAAAAAACH5BAEAAAsALAAAAAAKAAoAAAQfcMlJq12hIHKoSEqIdBIQnslknkoqfedIBQNikFduRQA7" border="0"></td>
  </tr>
 </thead>
 <tbody class="scrollContent bodyFormat" style="height:200px;">
   <tr class="alternateRow">
    <td>Maha</td>
    <td align="right">$19,923.19</td>
    <td align="right">100</td>
    <td align="right">100</td>
    <td>Owner</td>
    <td align="right">01/02/2001</td>
   </tr>
   <tr>
    <td>Thrawl</td>
    <td align="right">$9,550</td>
    <td align="right">159</td>
    <td align="right">100%</td>
    <td>Co-Owner</td>
    <td align="right">11/07/2003</td>
   </tr>
   <tr class="alternateRow">
    <td>Marhanen</td>
    <td align="right">$223.04</td>
    <td align="right">83</td>
    <td align="right">99%</td>
    <td>Banker</td>
    <td align="right">06/27/2006</td>
   </tr>
   <tr>
    <td>Peter</td>
    <td align="right">$121</td>
    <td align="right">567</td>
    <td align="right">23423%</td>
    <td>FishHead</td>
    <td align="right">06/06/2006</td>
   </tr>
   <tr class="alternateRow">
    <td>Jones</td>
    <td align="right">$15</td>
    <td align="right">11</td>
    <td align="right">15%</td>
    <td>Bubba</td>
    <td align="right">10/27/2005</td>
   </tr>
   <tr>
    <td>Supa-De-Dupa</td>
    <td align="right">$145</td>
    <td align="right">91</td>
    <td align="right">32%</td>
    <td>momma</td>
    <td align="right">12/15/1996</td>
   </tr>
   <tr class="alternateRow">
    <td>ClickClock</td>
    <td align="right">$1,213</td>
    <td align="right">23</td>
    <td align="right">1%</td>
    <td>Dada</td>
    <td align="right">1/30/1998</td>
   </tr>
   <tr>
    <td>Mrs. Robinson</td>
    <td align="right">$99</td>
    <td align="right">99</td>
    <td align="right">99%</td>
    <td>Wife</td>
    <td align="right">07/04/1963</td>
   </tr>
   <tr class="alternateRow">
    <td>Maha</td>
    <td align="right">$19,923.19</td>
    <td align="right">100</td>
    <td align="right">100%</td>
    <td>Owner</td>
    <td align="right">01/02/2001</td>
   </tr>
   <tr>
    <td>Thrawl</td>
    <td align="right">$9,550</td>
    <td align="right">159</td>
    <td align="right">100%</td>
    <td>Co-Owner</td>
    <td align="right">11/07/2003</td>
   </tr>
   <tr class="alternateRow">
    <td>Marhanen</td>
    <td align="right">$223.04</td>
    <td align="right">83</td>
    <td align="right">59%</td>
    <td>Banker</td>
    <td align="right">06/27/2006</td>
   </tr>
   <tr>
    <td>Peter</td>
    <td align="right">$121</td>
    <td align="right">567</td>
    <td align="right">534.23%</td>
    <td>FishHead</td>
    <td align="right">06/06/2006</td>
   </tr>
   <tr class="alternateRow">
    <td>Jones</td>
    <td align="right">$15</td>
    <td align="right">11</td>
    <td align="right">15%</td>
    <td>Bubba</td>
    <td align="right">10/27/2005</td>
   </tr>
   <tr>
    <td>Supa-De-Dupa</td>
    <td align="right">$145</td>
    <td align="right">91</td>
    <td align="right">42%</td>
    <td>momma</td>
    <td align="right">12/15/1996</td>
   </tr>
   <tr class="alternateRow">
    <td>ClickClock</td>
    <td align="right">$1,213</td>
    <td align="right">23</td>
    <td align="right">2%</td>
    <td>Dada</td>
    <td align="right">1/30/1998</td>
   </tr>
   <tr>
    <td>Mrs. Robinson</td>
    <td align="right">$99</td>
    <td align="right">99</td>
    <td align="right">(-10.42%)</td>
    <td>Wife</td>
    <td align="right">07/04/1963</td>
   </tr>
   <tr class="alternateRow">
    <td>Maha</td>
    <td align="right">-$19,923.19</td>
    <td align="right">100</td>
    <td align="right">(-10.01%)</td>
    <td>Owner</td>
    <td align="right">01/02/2001</td>
   </tr>
   <tr>
    <td>Thrawl</td>
    <td align="right">$9,550</td>
    <td align="right">159</td>
    <td align="right">-10.20%</td>
    <td>Co-Owner</td>
    <td align="right">11/07/2003</td>
   </tr>
   <tr class="total">
    <td><strong>TOTAL</strong>:</td>
    <td align="right"><strong>999999</strong></td>
    <td align="right"><strong>9999999</strong></td>
    <td align="right"><strong>99</strong></td>
    <td > </td>
    <td align="right"> </td>
   </tr>
 </tbody>
</table>
</div>
</td></tr>
</table>
</body>
</html>

是的,这适用于垂直滚动,但不适用于水平滚动。这是我在问题中提到的常见解决方案。 - pkaeding
这个解决方案相比其他一些方案有一个重大优势,那就是表结构仍然完整且合理。对于仅锁定标题行来说,这可能是最佳的解决方案。 - Mr. Shiny and New 安宇
感谢Shiny and New先生的评论。也许这值得为之投一票(+1)?(注意:这是社区帖子,对我没有声望增益) - VonC
替换为JSFiddle。 - Aaron Mason

0

这是我制作的一个纯JavaScript/CSS的示例。

https://jsfiddle.net/KirbyLWallace/x5sbe0dk/5/

这个页面本来是为全屏幕设计的,但我已经修改过以适应 fiddle 的特定宽度。

<body onResize="scaleElements()">

<div id="table-container-div">

<table id="data-table">
  <thead id="th-header">
    <tr id="th-header-row">
      <td>Column1</td>
      <td>Column2</td>
      <td>Column3</td>
      <td>Column4</td>
    </tr>
  </thead>

  <tbody id="tbl-body">
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
    <tr><td>a</td><td>b</td><td>c</td><td>d</td></tr>
    <tr><td>1</td><td>2</td><td>3</td><td>4</td></tr>
    <tr><td>h</td><td>i</td><td>j</td><td>k</td></tr>
  </tbody>

</table>

JavaScript:

(() => {
        scaleElements();  
    })();


  function scaleElements() {

        // element() is just shorthand for document.getElementById().

        // scaleElements() scales a number of other things, not included here, 
        // that get rescaled any time the browser, or a container is resized.
        // the table header row here is just one of them...
        //
        // this thing includes checks to see if a table with the table & thead 
        // is on the page.  If it is, it checks to see if the span container is
        // here (it's not on the first run, but it is on subsequent calls.  So, 
        // it adds it if it needs it, or reuses it if it's there.

        if (element("data-table")) {

            if (element("th-span-container")) {
                element("th-span-container").parentElement.removeChild(element("th-span-container"));
            }

            var x = document.createElement("div");
                x.id = "th-span-container";
                x.style.cssFloat = "left";
                x.style.position = "fixed";
                x.style.top = "10px";

                // you will want to fiddle with your own particular positioning. 
                // this one is positioned to work on a table that is below a page 
                // logo banner.

            var tds = element("th-header-row").getElementsByTagName("td");

            for (i = 0; i < tds.length; i++) {

                let z = tds[i];
                let y = document.createElement("span");

                y.style.padding = "0px";
                y.style.margin = "0px";
                y.style.fontFamily = "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif";
                y.style.fontSize = "13px";
                y.style.border = "0px";
                y.style.position = "absolute";
                y.style.color = "white";
                y.style.backgroundColor = "#3D6588";
                y.style.left = z.offsetLeft + "px";
                y.style.height = z.offsetHeight + "px";
                y.style.lineHeight = z.offsetHeight + "px";
                y.style.width = z.offsetWidth + "px";
                y.innerHTML = z.innerHTML;
                x.appendChild(y);

            }

            element("table-container-div").appendChild(x);
            element("th-header-row").style.visibility = "hidden";

        }

    }

function element(e) {
    return document.getElementById(e);
}

CSS:

body {
  background: black;
}

#table-container-div {
            width: 310px;
            position: absolute;
            top: 10px;
            bottom: 10px;
            overflow-x: hidden;
            overflow-y: auto;
            min-width: 320px;
        }

        table {
            font-size: 13px;
            height: 120px;
            width: 300px;
            border: 0px solid red;
            background-color: #11171F;
        }

        tr {
            height: 22px;
            color: #cff3ff;
        }

            tr:hover {
                background-color: dimgrey;
            }

        td {
          color:white;
            border-right: 1px dotted #4F4F4F;
        }

        #th-header-row {
            background-color: #3D6588;
            color: white;
        }

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