结论:必须用模拟表格并显式定义grid-template-columns,因有独立渲染逻辑导致表头与内容列宽脱节;grid容器需设高度+overflow-y:auto,表头用position:sticky;top:0;z-index:10,且所有行单元格须统一span方式、加min-width:0并处理滚动条空间。

直接说结论:用 display: grid 实现 Excel 式固定表头,必须放弃原生 <table> 结构,改用 <code><div> 模拟表格,并显式定义列轨道。否则表头和内容列宽必然脱节,滚动时错位。
<h3>为什么不能直接在 </h3>
<table> 上套 display: grid?
<p>浏览器对 <code><table> 元素有独立渲染逻辑,即使外层设了 <code>display: grid,<thead> 和 <code><tbody> 仍按表格语义计算尺寸,不参与 Grid 轨道对齐。结果就是:表头列宽和内容列宽各自算一套,滚动时完全错开。
<p>常见错误现象包括:<code><thead> 看起来“固定”了,但列与列之间明显偏移;或者 Safari 下完全不生效;又或者滚动条出现后右侧列被挤压变形。
<ul><li>禁用 <code><table>、<code><tr>、<code><td> 标签,全部换成 <code><div>
<li>整个容器设 <code>display: grid,并用 grid-template-columns 显式声明每列宽度(如 120px 1fr 150px)
<thead> 和 <code><tbody> 对应的 <code><div> 必须是该 Grid 容器的**直系子元素**
<h3>如何让表头真正 sticky 且不被内容穿透?</h3>
<p>关键不是 <code>z-index 数值够不够大,而是 stacking context 和滚动上下文是否正确建立。很多项目加了 z-index: 999 还是被盖住,问题出在层级结构没理清。
- Grid 容器必须设明确高度(
height或max-height)+overflow-y: auto - 表头容器(如
.thead)设position: sticky; top: 0; z-index: 10 - 内容容器(如
.tbody)不要设position,但内部每个行级<div> 要设 <code>min-height: 0防止撑高整行 - 避免给
.tbody或其父容器加transform、will-change,否则会创建新 stacking context,让z-index失效 - 所有行(表头行 + 数据行)都必须使用相同的
grid-column: span 1分配方式,不能有的用grid-column: 1 / -1,有的靠顺序自动占位 - 每个单元格(
.td)加min-width: 0,防止长文本强制撑开列宽 - 为防滚动条挤占空间,建议在 Grid 容器上加
scrollbar-gutter: stable both(Chrome/Edge/Firefox 支持),或手动预留padding-right: 16px
列宽对齐的三个硬性约束
Grid 不像表格有自动列对齐语义,所有列宽一致性都靠手动约束。漏掉任意一条,滚动时就会“跳格”。
最易被忽略的点:display: contents 的使用场景有限制——它会让元素失去盒模型,如果单元格需要边框、背景或 padding,就不能简单套用;此时得改用 grid-column 显式定位,而不是依赖 DOM 顺序。











