大表格卡顿的根因几乎从不是 <table> 标签本身慢,而是 DOM 节点数量过大导致布局(Layout)和绘制(Paint)成本线性上涨。结论:优化优先级应该是「先砍节点数 → 再砍每次重排 → 最后才是 CSS 微调」,只调 CSS 不动节点,1 万行的表格该卡还是卡。
大表格为什么会卡?
超过 5000 个 <td> 就该警惕,超过 1 万个基本必卡。一行 10 列的表格,1 万行就是 10 万个单元格节点,每个节点都要参与样式计算、布局和绘制三轮处理。默认 table-layout: auto 下浏览器还得遍历单元格内容去反推列宽,这是典型的 O(n) 甚至更贵的操作。先用 Chrome DevTools 的 Performance 面板录一段滚动,如果 Layout 和 Recalculate Style 占了主线程大头,方向就找对了。
第一步:怎么把 DOM 节点数降下来?
结论:分页和虚拟滚动是唯一量级的解法,其他都是修修补补。分页最简单,每页放 50-100 行即可。要保留「一屏滚动看全部数据」的体验,就用虚拟滚动:只渲染可视区,上下各留一段缓冲。典型配置是可视区 30 行加 10 行 buffer,实时 DOM 只有 40-50 行,滚动时复用节点、只换内容。React 生态用 react-window 或 TanStack Virtual,Vue 用 vue-virtual-scroller。虚拟滚动的前提是行高必须固定,因为库要靠 itemSize 算总高度和偏移量;行高不定就得逐个测量,性能收益会被吃掉一大半。
table-layout: fixed 到底能省什么?
结论:table-layout: fixed 把列宽计算从「遍历所有单元格」变成一次定宽分配,是大表格性价比最高的一行 CSS。配合 <colgroup> 给每列写死宽度,浏览器拿到第一行就能确定全部列宽,不再关心后续单元格内容多长:
<table style="table-layout:fixed;width:100%">
<colgroup>
<col style="width:80px"><col style="width:200px"><col>
</colgroup>
注意点:加了 fixed 之后超出宽度的文字不会撑开列,必须自己加 overflow:hidden;text-overflow:ellipsis;white-space:nowrap,否则内容会被裁掉。另外不要给每个 <td> 挂一长串类名,层级很深的选择器(如 .a .b td)会显著拉高样式重算成本,能用 nth-child 或元素选择器就别写嵌套。
怎么减少滚动时的重排和绘制?
结论:把表格从文档流里「隔离」出去,并避免在 td 上做强绘制属性。给表格容器加 contain: strict,或在行上使用 content-visibility: auto(Chrome 85 起支持,Firefox 125、Safari 18 也已跟进),浏览器会跳过屏幕外行的渲染。同时避开三个常见坑:一是 td 上别用 box-shadow、渐变背景、border-radius,这些会让每次滚动都触发大量绘制;二是不要在滚动回调里读 offsetHeight、scrollTop 这类属性,它会强制同步布局(forced synchronous layout),把批量重排拆成一次次单帧重排;三是插入数据别用字符串拼接 innerHTML 赋值几万行,改用 DocumentFragment 拼好再一次性 append,或者分批放到 requestAnimationFrame 里渲染。
表头固定和边框怎么处理?
结论:粘性表头用 position: sticky,但要注意它和 border-collapse: collapse 的冲突。直接写 thead th { position: sticky; top: 0; z-index: 2; background: #fff } 即可,Chrome、Firefox、Safari 现代版本都支持。坑在于 border-collapse: collapse 下 sticky 单元格的边框滚动时会消失,解决办法是改成 border-collapse: separate; border-spacing: 0,再用 box-shadow: inset 0 -1px 0 #ddd 模拟下边框——注意这里用的是 inset shadow,不影响前面说的绘制性能。
什么时候该放弃 DOM 表格?
结论:列数超过 30 列、或多达 10 万行以上时,DOM 方案的天花板就到了。这时考虑 Canvas 渲染表格(AG Grid 的 canvas 模式、或自研 canvas 表格),它把整张表当一张位图绘制,行数翻倍对帧率几乎无影响,代价是失去原生文本选中、无障碍和 CSS 样式能力。如果数据量在 10 万行级别但只需浏览,服务端分页加搜索通常是更划算的选择。
总结一下执行顺序:先确认单元格总数,超 5000 上分页或虚拟滚动砍掉 95% 以上节点;再上 table-layout: fixed 加 colgroup 定宽;然后用 content-visibility 或 contain 隔离渲染、避开 td 上的重绘制属性和滚动中的同步布局读取;最后才考虑 sticky 表头的边框写法这类细节。真到 30 列或 10 万行以上,再评估 Canvas 方案。