做后台管理系统的前端绕不开一张表格。Element UI 的el-table我用了好几年被问得最多的问题不是“这个表格怎么渲染数据”而是数据一多表格一长表头跟着页面滚走了根本分不清哪一列是哪一列怎么办不少人第一反应是去找各种第三方插件、封装组件甚至自己用 JS 监听滚动事件去模拟一个固定表头。其实这件事的真实解法简单到让人想笑——Element UI 自己早就把表头固定做完了你只需要给el-table加一个height或者max-height属性。这篇博文把我踩过的坑、试过的方法和能直接抄的配置都整理出来前半部分聊 Element UI 自带方案后面补上原生表格的position: sticky实现最后重点排查那些“明明设置了高度但表头还是跑了”的疑难杂症。不管你是刚入行的前端小白还是被表格折磨已久的老手这篇内容都能帮你少走不少弯路。1. 先搞明白Element UI 的表格为什么天生就能固定表头很多教程一上来就丢代码让人复制完就跑。但表头固定这件事如果不理解底层机制遇到一点意外场景就抓瞎。所以先把原理说清楚后面所有配置你都自己能推出来。1.1 表头和数据区其实是两张独立的表你可以打开开发者工具看下一个普通el-table渲染出来的真实 DOM 结构。表面上它是一张表格实际上内部被拆成了若干个部分.el-table__header-wrapper里放的是表头.el-table__body-wrapper里放的是数据区还有空数据提示、固定列区域等。表头是一个独立的table数据区也是另一个独立的table。这意味着什么意味着表头和数据区天然就是割裂的它们只是视觉上被拼在一起。所以只要能让数据区自己产生滚动条表头自然就“固定”在顶部——因为滚动只发生在数据区内部表头根本没有跟着滚动的机会。这个设计其实是从原生表格的痛点来的。原生table的表头和数据是一个整体页面一滚动thead就会跟着tbody一起跑。Element UI 把两张表分开渲染等于是用结构换来了表头固定的能力。了解到这一步你就能明白凡是遇到“表头固定不住”的问题第一反应不应该是找什么高级技巧而是检查数据区的滚动容器有没有被正确触发。1.2 让表体自己滚动的最小触发器height 与 max-height既然表头固定依赖“数据区滚动”那问题就从“怎么固定表头”变成了“怎么让数据区出现滚动条”。在el-table里这件事只需要一个属性——height或者max-height。height给数据区设置一个固定高度。不管数据量多还是少数据区都占这么高数据多了内部滚动数据少了下方留白。max-height给数据区设置一个最大高度。数据量少的时候表格自动收缩数据量超过这个值才出现内部滚动条。看起来很基础但很多人恰恰是在这里犯了错。我见过不少同事明明写了height但数据区还是跟着页面滚仔细一查height写成了styleheight: 400px而不是height属性。这两个完全不是一个东西样式上的高度会影响布局但只有在el-table上通过height或max-height属性传入时组件才会在内部分配表头区和数据区的高度并给数据区挂上滚动逻辑。另外如果同时设置了height和max-heightheight优先生效max-height不会起作用。实际开发中没必要两个一起写一个就够了。不定高度就选max-height需要严格占据固定空间就选height。1.3 外面再套一层 overflow 会毁掉一切还有一种很典型的情况有人确实给el-table设置了height但表格外面又包了一个div styleoverflow: auto结果表头又跟着页面跑了。原因很简单外层这个 div 本身变成了滚动容器当你在页面上滚动时滚动行为发生在外层 div 上整个表格包括表头作为一个整体被滚动了。真正想让el-table内部滚动表格外面不应该再套一层可以滚动的容器。最常见的是页面本身滚动而你只希望表格数据区独立滚动。这时候给el-table一个合适的高度就行不要在外层再用overflow: scroll或者overflow: auto去包它。这个原理搞清楚之后下文的配置就变得顺手多了。2. 超简单实操两行配置实现表头固定接下来直接进入实操。这也是全文最核心、最“超简单”的部分。2.1 最基础写法固定高度与最大高度怎么选最简单的情况假设你的表格固定占据页面中间一块区域直接写template el-table :datatableData height560 el-table-column propname label姓名 / el-table-column propage label年龄 / el-table-column propaddress label地址 / /el-table /templateheight可以传数字也可以传字符串。传数字时单位是像素传字符串时需要带上单位比如560px、calc(100vh - 180px)。如果你不确定表格数据有多少尽量用max-heightel-table :datatableData max-height560这样数据只有两条时表格不会留出一大片空白数据有一千条时数据区出现滚动条并且默认停留在顶部附近。根据我的经验大多数后台管理系统的表格页面优先考虑max-height更稳妥。产品经理很少要求表格必须占满一整块固定高度反而是希望数据少的时候页面紧凑一点。只有当你清楚知道自己需要分配多少空间时才直接用height。2.2 页面自适应高度像素、百分比还是 calc很多场景下表格不是单独存在的。页面顶部通常有标题栏、面包屑、筛选条件、分页器表格夹在中间。这时候最烦的就是给表格估算一个合适的像素高度屏幕矮一点表格底部被截断屏幕高一点表格下方又空出一大块。解决办法是用calc动态计算el-table :datatableData heightcalc(100vh - 180px)这里的180px是你页面中除了表格外的其他部分总高度包括顶部菜单、卡片边距、标题、筛选区域、分页器等等。不同页面这个数值不一样需要自己按实际量一下。还有更严谨的做法用 flex 布局撑开容器然后让表格高度等于父容器剩余空间的高度。比如div classpage-container div classtoolbar筛选条件/div div classtable-wrap el-table :datatableData height100% ... /el-table /div div classpagination分页/div /div.page-container { display: flex; flex-direction: column; height: calc(100vh - 120px); } .table-wrap { flex: 1; min-height: 0; }这里的关键点在于el-table的height100%只有在父容器table-wrap有明确高度时才有效。而table-wrap的高度来自 flex 布局的flex: 1所以这套组合拳打出来表格就能自动撑满剩余空间同时又不会把页面撑出滚动条。注意min-height: 0这个样式。flex 容器里的子元素默认min-height: auto内容多了会把容器撑破。不加min-height: 0表格高度可能仍然会溢出这也是一个容易忽略的细节。2.3 完整示例一套能直接抄的模板布局放一个我常用的完整模板配合分页器一起template div classtable-page div classsearch-bar el-input v-modelkeyword placeholder搜索 / el-button typeprimary查询/el-button /div el-table :datalist heightcalc(100vh - 220px) v-loadingloading el-table-column propid labelID width80 / el-table-column propname label名称 min-width140 / el-table-column propstatus label状态 width100 / el-table-column propcreatedAt label创建时间 width180 / /el-table el-pagination layouttotal, prev, pager, next :totaltotal :page-sizepageSize current-changeloadData / /div /template这个写法的好处是分页器在表格外面表格只负责自己那一块的滚动不管翻到第几页表头纹丝不动。很多初学者会担心翻页之后表头会不会跑掉实际上翻页只是更新了data表格的高度和布局结构没有变化表头固定状态不会受到任何影响。3. 不依赖 Element UI原生 table 的 position: sticky 固定法有时候项目没有引入 Element UI或者你只是在写一个简单的静态页面又或者产品经理要的表头固定方式和el-table默认方案不一样。这时候就该原生 CSS 的position: sticky出场了。3.1 sticky 的原理与适用边界position: sticky可以理解成relative和fixed的结合体。元素在滚动到某个阈值之前按照正常文档流排列滚动到阈值之后它就像fixed一样贴在屏幕上。但它的“贴”不是相对于整个视口而是相对于最近的滚动容器。它最经典的用法就是.sticky-bar { position: sticky; top: 0; }只要这个元素还在滚动容器内它到达顶部之后就会一直悬浮在顶部。一旦滚出容器范围就不会再悬浮了。这个特性让sticky非常适合做表头固定。原生表格里表头是thead你只要让th保持粘性定位滚动表体时表头就会停住。3.2 原生表格固定表头顶部代码与三个必改样式原生表格的写法比较直观table classlist-table thead tr thID/th th姓名/th th部门/th th入职时间/th /tr /thead tbody tr td1/td td张三/td td前端组/td td2022-03-01/td /tr !-- 更多行 -- /tbody /table然后加 CSS.list-table { border-collapse: separate; border-spacing: 0; } .list-table thead th { position: sticky; top: 0; background: #fff; z-index: 1; }这段代码看着简单但至少有三个坑是必须注意的。第一个坑是border-collapse。border-collapse: collapse在某些浏览器下会和position: sticky冲突表头滚动时可能出现半截边框消失或者粘不住的现象。所以必须改成border-collapse: separate; border-spacing: 0再用td、th上的border-bottom去补分割线。第二个坑是背景色。position: sticky之后元素还在文档流中它背后的内容会透过来。如果不给th设置一个不透明的background你会看到表格数据在表头底下钻来钻去。这个现象很常见但排查起来并不直观很多新手以为是自己代码写错了。第三个坑是层级问题。表体里的行如果有高亮效果比如tbody tr:hover { background: #f5f7fa; }表体内容在向上滚动穿越表头区域时会盖在表头上。解决办法是给th设置z-index: 1让它盖住后面的内容。3.3 sticky 方案与 el-table 内置方案的取舍那么问题来了既然position: sticky能实现为什么还要用el-table的height属性因为两者解决的问题不完全一样。el-table的height方案是让数据区自己滚动表头和数据区分离表头天然固定。整个表格的高度是受控的和页面其他部分互不干扰。而sticky方案是表格整体跟随页面滚动的只是表头到了顶部以后“暂时停住”。表格多长页面就要滚多远。如果你的页面里表格是主要内容希望表格数据区独立滚动用el-table的height最合适。如果你的整个页面都在正常流里滚动表格只是页面上的一部分且表头需要悬浮覆盖在页面其他内容上方那sticky更自然。实际项目中这两种方案往往搭配使用并不互斥。4. 场景化适配弹窗、动态列和多表格单一页面的表格固定很简单一旦遇到弹窗、动态列、多个表格同屏各种奇奇怪怪的问题就冒出来了。这一章专门处理这些高频场景。4.1 弹窗里的表格为什么经常“失灵”在el-dialog里放一个表格设置了max-height但表头还是跟着弹窗内容滚动。这个问题我处理过好几次原因通常出在弹窗的结构上。弹窗整体是一个浮层里面的滚动区域是.el-dialog__body。如果表格放在弹窗 body 里并且 body 本身高度超出了视口浏览器滚动的是弹窗 body 而不是表格的数据区。这时候你给表格设置的max-height并不一定能覆盖住弹窗自身的滚动行为。解决办法之一是在弹窗内容区域用 flex 布局把表格区域限定为一个可伸缩的空间el-dialog title详细数据 width800px :visible.syncvisible div classdialog-table-wrap el-table :datatableData max-height400 ... /el-table /div /el-dialog这里max-height400会强制表格数据区最大高度为 400px超出部分在表格内部滚动。弹窗的 body 不再需要滚动。如果弹窗本身很高需要根据弹窗高度动态计算表格高度那就用上文的calc思路el-table :datatableData :heightdialogTableHeight其中dialogTableHeight可以在弹窗打开后通过nextTick获取弹窗 body 的高度再减去固定偏移量计算得到。这个方法比写死400更通用但代码量多一截。注意el-dialog里放表格时表格的height或max-height属性不能省。如果只依赖弹窗滚动表头固定这件事一定会出问题。4.2 动态列与 v-if 渲染造成的错位还有一种情况也很常见表格的列不是写死的而是根据后端返回的字段动态生成。列用v-for渲染某些字段可能用v-if控制显隐。这时候表格渲染初期可能列还没加载完等数据到达之后表格重新布局表头和数据区因为渲染时机不同列宽对不上。这种情况下很多人会怀疑是固定表头导致的错位。其实表头固定本身没有错错的是表格在列变化后没有重新计算布局。常见解决办法有两个。一个是在获取到数据、列结构确定后手动调用表格的doLayout方法el-table reftableRef :datatableData ... /el-tablethis.$nextTick(() { this.$refs.tableRef.doLayout() })另一个更简单粗暴的办法是给el-table加上:key让列结构变化时强制重新渲染整个表格el-table :keycolumnVersion :datatableData每次列结构更新时把columnVersion加一表格就会重新创建宽度计算从头来一遍。代价是重渲染开销大一点但对大多数中后台页面来说影响可以忽略。4.3 一页多个表格各自滚动互不干扰页面上放了两个、三个甚至更多el-table每个都设置了自己的height或max-height它们会不会互相影响答案是基本不会。每个el-table都是独立组件内部滚动容器是各自的表头固定状态互不干扰。唯一需要注意的场景是多个表格放在同一个弹性容器里可能导致总高度超过视口页面出现两层滚动条体验很差。这时候要给每个表格划定明确的高度或者让外层容器整体滚动而不是让每个表格都滚动。我以前遇到过一个问题页面上方是汇总表格下方是明细表格两个表格都设置了自适应高度。结果汇总表格高度撑开明细表格被挤到视口外用户需要滚好几屏才能看到明细表十分难受。后来改成上方汇总表格用固定高度下方明细表格用剩余空间高度两个区域各自滚动体验就正常了。5. 表头错位等常见问题排查实录这一部分把我的排查经验整理出来。表格出问题的时候与其一个个试玄学方案不如按套路来。5.1 先学会用开发者工具诊断遇到表格异常我建议你第一步打开开发者工具直接查看.el-table__body-wrapper的样式。如果它没有overflow: auto或overflow-y: auto说明表格组件根本没有进入滚动模式表头不会固定。检查是否真的传了height或max-height。如果它有overflow: auto但滚动条没有出现说明内容高度没有超过容器高度数据量不够这是正常现象。如果它内部有滚动条但滚动时表头也跑了去看表格外层是不是有别的滚动容器比如overflow: auto的父 div。如果表格出现了滚动条但表头位置偏离检查表头区和数据区的宽度是否一致。这套检查流程能解决掉 80% 的表头固定问题。说白了表头固定本质上就是一个滚动容器归属问题找到滚动发生在谁身上问题就解决了一半。5.2 高频问题与对应解法我再列举一些实际开发中反复出现过的问题和解法。第一个是“表头固定后表头下面出现一条空白”。这个通常是固定列fixed属性和滚动条宽度占位导致的。Element UI 在表头固定的情况下为了让横向滚动条不遮挡内容会预留一个滚动条宽度的间隙视觉上表现为右侧多出一条细窄的空白。这是组件正常行为不是 bug不用管。第二个是“表头和数据区的列对不齐”。我之前排查过一个案例表格列里有一列用v-if控制显隐数据变化后表头已经变了但表体宽度还是旧的。当时我用监听器在数据变化后调用doLayout()解决的。这类问题优先考虑“强制重算布局”。第三个是“弹窗打开时表格表头正常但弹窗宽度变化后表头错位”。原因是弹窗在打开过程中宽度有一个过渡动画表格在动画未完成时就完成了渲染宽度计算基于过渡中间态。等动画结束表格宽度变了表头区数据区就错位了。解决办法还是等待动画结束或者弹窗opened事件触发后再调用doLayout。第四个是“表格在v-show的隐藏容器中渲染显示后表头错位”。v-show只是把元素设为display: none表格在隐藏状态下宽度计算为 0。等它显示出来表格宽度并没有自动重新计算。解决办法是在显示后调用doLayout或者用v-if延迟渲染表格。5.3 一张速查表应对 90% 的表格异常把高频问题整理成表格方便你遇到问题时快速查阅现象可能原因推荐解法表头跟着页面滚动没有传入height或max-height在el-table上添加height或max-height表头跟着外层容器滚动表格外层又套了可滚动 div去掉外层overflow让表格自身滚动表头与表体列宽不一致动态列、隐藏容器渲染、弹窗宽度动画在nextTick或弹窗opened后调用doLayout()表格翻页后表头错位数据更新导致表格重新渲染时布局未重算监听数据变化后调用doLayout或给表格加key弹窗内表格表头固定失败弹窗 body 自身滚动覆盖了表格滚动给表格设置max-height并把弹窗 body 滚动去掉表头下方横向滚动条遮挡最后一列固定列与滚动条占位冲突无需处理属 Element UI 正常表现原生 table 表头粘不住border-collapse: collapse与 sticky 冲突改用border-collapse: separate; border-spacing: 0原生 table 表头背景透明未设置background给th添加不透明背景色6. 一个容易混的需求表头悬浮与固定列最后补充一个容易混的需求点因为我在实际项目里经常被问到也经常看到有人把方案用错。6.1 表格内部滚动和整页滚动是两回事“固定表头”在需求上其实有两种理解。一种是“表格数据区域固定高度内部滚动表头固定不动”这是 Element UI 的height、max-height方案解决的。另一种是“整个页面正常滚动表格在页面里但当用户滚动页面时表头悬浮在视口顶部”这往往是产品经理说的“表头吸顶”或者“表头悬浮”。后者的实现不能靠el-table的height属性因为那样表格区域就变成独立滚动了和页面整体滚动产生两层滚动条体验反而割裂。这种情况更适合给整个表格的外层容器加上position: sticky; top: 0但需要注意表格本身的高度是内容高度表头粘住以后表体会继续往上滚动穿越表头体验取决于你具体想达到什么样的视觉效果。我的建议是如果产品经理没有强制要求“整页滚动时表头悬浮”优先用el-table的height方案把表格做成内部滚动。它代码量最少体验也最稳定。如果一定要整页滚动加表头悬浮那就得自己写定位逻辑不能再指望组件自带的能力。6.2 fixed 固定列什么时候会和表头固定打架el-table上除了固定表头还经常固定某些列比如最左侧的 ID 列、最右侧的操作列。用fixed属性就能实现el-table-column propid labelID width80 fixedleft / el-table-column propaction label操作 width120 fixedright /固定列和表头固定可以叠加使用不会冲突。但要小心两个副作用。第一是固定列数量不要太多每列都渲染一套独立的表格数据列数过多时滚动性能会明显下降。第二是固定列和滚动条占位的叠加效果会让小屏幕下表格可用宽度大幅缩水。碰到这种情况优先检查列宽设计而不是继续堆fixed。还有一种常见误区是为了固定操作列把操作列fixedright同时整个表格又设置了height导致固定列区域出现阴影遮挡或背景色重叠。Element UI 默认会处理这部分样式只要你用的是标准el-table一般不会出大问题。但如果遇到遮挡先检查外层是否有自定义background或box-shadow冲突。最后再分享一个我个人的体会表格相关的问题90% 都不是代码能力问题而是对组件机制的理解问题。我自己最早也是遇到表头滚动就去百度各种封装方案折腾半天后来老老实实把 Element UI 中文官网的 Table 文档从头到尾读了一遍才发现height、max-height、fixed这三个字段已经覆盖了绝大多数需求。遇到表格布局错位、固定失效先别慌着重写代码打开开发者工具看滚动容器在哪里、宽度计算对不对往往几分钟就能定位到原因。这个习惯帮我省下了大量重复排查的时间也推荐你试一试。
阅读完成 · 觉得有帮助?