跟着官方示例学习 @tanStack-table ---Sticky Column Pinning

45 阅读3分钟

🌲系列一:跟着官方示例学习 @tanStack-table --- Basic

🌲系列二:跟着官方示例学习 @tanStack-table --- Header Groups

🌲系列三:跟着官方示例学习 @tanStack-table --- Column Filters

🌲系列四:跟着官方示例学习 @tanStack-table --- Column Ordering


🧠 什么是 Sticky Pinning?

Sticky Pinning 让表格的列在横向滚动时保持固定在左侧或右侧,这一功能非常实用,尤其是在展示大量列数据时,可以让用户始终看到关键字段,比如姓名或操作按钮。就像 Excel 的“冻结窗格”一样。

这是通过结合 position: stickyleft/right 等样式实现的,而不是传统的 position: fixed,因此更自然地嵌入表格布局中。

🛠 如何实现 Sticky Pinning?

✅ 第一步:启用列 Pinning 功能

TanStack Table 提供了一套 pinning API,不过需要自己定义交互和样式。官方并没有内置样式,但我们可以使用如下样式函数来统一管理:

const getCommonPinningStyles = (column: Column<Person>): CSSProperties => {
  const isPinned = column.getIsPinned();
  const isLastLeftPinnedColumn =
    isPinned === "left" && column.getIsLastColumn("left");
  const isFirstRightPinnedColumn =
    isPinned === "right" && column.getIsFirstColumn("right");

  return {
    boxShadow: isLastLeftPinnedColumn
      ? "-4px 0 4px -4px gray inset"
      : isFirstRightPinnedColumn
      ? "4px 0 4px -4px gray inset"
      : undefined,
    left: isPinned === "left" ? `${column.getStart("left")}px` : undefined,
    right: isPinned === "right" ? `${column.getAfter("right")}px` : undefined,
    opacity: isPinned ? 0.95 : 1,
    position: isPinned ? "sticky" : "relative",
    width: column.getSize(),
    zIndex: isPinned ? 1 : 0,
  };
};
  1. column.getIsPinned() 获取当前列是否被 Pin(固定)
  2. isLastLeftPinnedColumnisFirstRightPinnedColumn 判断是否是“最后一个左固定列”或“第一个右固定列”,这些判断用于添加分隔视觉效果(如阴影 box-shadow)。如果有多个左/右固定列,不希望它们每一个都加阴影,只需加在“最左/右边那个”上。
  3. getStart("left") 返回从表格左边到当前列开始位置的总宽度。getAfter("right") 返回当前列之后所有右固定列的宽度。这两个值动态决定 sticky 的偏移量,确保列对齐。
  4. position: isPinned ? "sticky" : "relative" !!! 最重要的是:固定列用 position: sticky,其余用默认的 relative

🧪 添加 Pin 按钮交互

使用 column.pin() 方法将列固定到 "left""right",或使用 false 取消固定。

{header.column.getIsPinned() !== "left" && (
  <button onClick={() => header.column.pin("left")}>{"<="}</button>
)}
{header.column.getIsPinned() && (
  <button onClick={() => header.column.pin(false)}>X</button>
)}
{header.column.getIsPinned() !== "right" && (
  <button onClick={() => header.column.pin("right")}>{"=>"}</button>
)}

💻 实战示例

结合 TanStack TableAPIgetCommonPinningStyles 函数,渲染 theadtbody 中的单元格:

<th style={{ ...getCommonPinningStyles(column) }}>
  {flexRender(header.column.columnDef.header, header.getContext())}
</th>

<td style={{ ...getCommonPinningStyles(column) }}>
  {flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>

💡 border-collapse: separate

💡 注意:

getCommonPinningStyles 上方的注释中,最后一行标注

View the index.css file for more needed styles such as border-collapse: separate

你需要保证 <table> 元素使用了 border-collapse: separate,以防止 sticky 被破坏。

查阅官方的源码中 index.css 文件

table {
  /* box-shadow and borders will not work with positon: sticky otherwise */
  border-collapse: separate !important;
  border-spacing: 0;
}

作用:让 position: stickybox-shadow 等样式在 <td>/<th> 上生效

默认情况下,HTML <table> 使用的是 border-collapse: collapse,这会让所有 <td><th> 之间的边框合并。这种合并有两个问题:

它会让单元格之间没有“独立的视觉盒子(box)”,从而导致 position: stickybox-shadow 在某些浏览器中无效或表现异常。

所有单元格行为都被 table 主体“吞掉”,无法形成单独的 sticky 层级。

✅ 所以必须强制设为 border-collapse: separate,让每个 cell 成为 独立可控制的盒子,从而支持:

  • position: sticky

  • z-index

  • box-shadow

  • overflow: hidden

等高级样式

❗️加上 !important 是为了覆盖某些浏览器或组件库中对 <table> 的默认样式设置。

Jun-08-2025 08-41-38.gif

🔔 对官方示例代码可能存在一些删减的情况

代码地址🔗:Gitee

官方代码地址🔗: @tanStack/react-table