尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Element表格固定列踩坑指南:错位、阴影与CSS sticky替代方案

Element表格固定列踩坑指南:错位、阴影与CSS sticky替代方案 写 Element 表格固定列踩坑是每个用 Vue 做后台系统的人迟早都要经历的一关。我在好几个中后台项目里都碰上过这事特别是 element table 配上 fixed 之后表头错位、列宽漂移、莫名其妙的阴影、滚动条穿透几乎被这些问题轮番折磨过。这篇就把我实际排查和修复固定列问题的完整过程整理出来从底层结构到具体的代码写法都讲清楚给正在和固定列搏斗的朋友一份可以直接照着操作的排查手册。1. 固定列的真实面目它不是 style是“三张表”在同步表演1.1 固定列在 DOM 层面上到底干了什么很多人在排查固定列问题的时候第一反应是打开浏览器开发者工具想去看看那个 fixed 列是不是用了position: fixed或者position: sticky。实际上 Element 的实现思路完全不是这样。Element UI 和 Element Plus 在渲染带固定列的表格时会把整个表格从 DOM 结构上拆成三个独立区域左侧固定区fixed-left 区域右侧固定区fixed-right 区域主体可滚动区域el-table__body-wrapper这三个区域内各自包含一套完整的el-table__header和el-table__body也就是说同一份表头和表体数据在页面里其实渲染了不止一份。左侧固定区的表头和数据行是复制出来的一份“克隆版本”右侧也是。组件通过给这三个区域设置不同的transform或margin-left让它们在视觉上拼成一个完整的表格。这个设计的好处是滚动时固定列完全独立不会跟着主体区域水平滚动。坏处也很明显只要这三份 DOM 中的任何一份宽度计算出现偏差视觉上就会出现错位、遮挡、重复边框等问题。理解了这个“三张表”的结构后面所有排查思路都围绕一个核心目标——让三份表格的列宽完全一致。1.2 我最初踩的坑以为固定列就是 position: fixed我第一次用固定列的时候本能地以为 Element 是拿 CSS 的position: fixed把某一列钉住的。所以当我发现固定列和主体列在某一列宽度变化后出现对不齐第一反应就是自己写 CSS 去覆盖。我在项目里给.el-table__fixed写了一堆类似right: 0、left: 0的样式结果滚动条疯狂穿帮固定列的边框也对不上。后来看到源码才意识到el-table__fixed只是一个普通的绝对定位容器它的宽度是由内部表格自动撑开的真正控制固定列位置的是组件内部维护的一个fixedWidth状态值。从那时候起我养成了一个习惯遇到固定列样式问题先看控制台里固定容器和主体容器宽度是否一致再去动 CSS。盲目覆盖样式只会让三个区域之间的宽度关联彻底断开。1.3 同一个项目里 fixed 的三个常用位置根据我做过的一堆后台管理页面固定列一般就出现在三个位置左侧第一列通常是多选框列或者序号列因为勾选和查看序号是高频操作滚到右边的时候必须看得见。最右侧一到两列一般是操作列放编辑、删除、详情按钮。左侧前两列业务单据类表格比如同时需要固定“单号”和“状态”两列。这三个位置看着简单但组合起来之后左右两侧同时固定时组件需要同时维护两套 fixed 数据出问题的概率会成倍上升。后续所有排查都要以这个场景为前提。2. 固定列高频翻车现场从 2.11.4 的“莫名阴影”说起2.1 阴影到底是哪来的很多人在网上搜过“element plus 2.11.4 表格偶尔会出现莫名奇妙的阴影”。我遇到这个问题的时间点和网上说的 2.11.4 版本高度重合。当时现象是表格横向滚动时固定列和主体区域的分界线上会突然闪出一条横向的阴影带接着又消失没有固定规律。这条阴影不是 Element 故意加的装饰。查看 Element Plus 源码会发现固定列的容器.el-table__fixed在滚动时会根据滚动位置动态添加一个is-scrolling-left或is-scrolling-right的 class。每个 class 对应一条有渐变效果的滚动阴影.el-table .el-table__fixed-right::before, .el-table .el-table__fixed::before { content: ; position: absolute; top: 0; bottom: 0; width: 10px; z-index: 1; pointer-events: none; }组件里会用一个scrollPostion对象记录当前滚动方向和位置当鼠标或触摸板触发的滚动事件频率特别高或者滚动容器被多个组件同时监听时这个位置的更新会晚半拍。于是阴影 class 被加上但对应的状态值还没有同步阴影就“卡”在了一个错误的位置上。2.2 为什么“偶尔”才会出现这个“偶尔”其实是多种条件叠加的结果。我复现了很久最终确定的条件组合是这样的表格位于某个overflow: auto的页面容器内表格内容需要横向滚动页面本身还会跟随鼠标滚轮竖向滚动浏览器是 Chromium 内核且开启了平滑滚动只要这几个条件同时满足系统级滚动和表格组件内部的滚动监听就会竞争同一个事件循环。固定列阴影的显示状态是通过计算滚动位置来决定的而滚动位置又被全局滚动占用了两者打架就会造成阴影闪现。我之所以强调这个版本是因为 2.11.4 之前固定列阴影的渐变层是常驻的只是透明度变化用户感知不强2.11.4 之后组件调整了阴影的显隐逻辑改成了动态插入和移除所以这个“卡状态”的问题就突然变得明显了。2.3 我的修复思路不用覆盖组件自己控制阴影如果你也被这个阴影闪动折磨最简单的方案其实不是去改 Element Plus 源码而是让阴影根本不存在。在覆盖样式里写.el-table .el-table__fixed::before, .el-table .el-table__fixed-right::before { display: none; }如果你还是想要滚动到边界时有提示效果可以自己实现一层渐变遮罩用transition控制透明度。这样阴影的出现和消失完全由 CSS 过渡控制不再受那个滚动状态值的影响。这个方法我放在生产环境上跑了几个月没有再出现过阴影闪动。我认为这属于“与其和别人的实现细节较劲不如绕开它”的典型例子。3. 错位问题全链路排查宽度、渲染时机、数据刷新3.1 先搞懂 doLayout 是干什么用的固定列错位十有八九都和宽度计算有关。Element 表格在挂载时会遍历每一列把指定宽度和实际渲染宽度都存进内部状态。表头和表体各存一份然后通过el-table__inner-wrapper把它们对齐。如果你在控制台执行vm.$children.find(item item.$options.name ElTable).doLayout()你会发现表格错位瞬间恢复。这说明组件内部其实有能力重新计算并修正宽度只是某些时候没有自动触发布尔值。doLayout做的事情简单说就是强制刷新所有表格区域的高度和宽度重新执行一次布局计算。所以排查错位问题的第一件事就是先判断是“计算错了”还是“没有重新计算”。3.2 错位触发的三种典型时机我在项目里总结出的错位高频场景基本集中在三个时机第一种卡片或弹窗打开后表格宽度变了但组件不知道。比如表格放在el-dialog里弹窗打开时带了一个缩放动画表格在这个动画过程中完成初始化拿到的初始宽度是缩放过程中的中间宽度的动画结束后弹窗宽度变大表格没有重新测量于是固定列就停在了一个偏窄的位置上。第二种切换 Tab 页签时隐藏的表格被显示出来。如果表格初始在display: none的容器里等切回来显示时表格并不会自动触发宽度重算。这种场景下所有列宽都会乱不只是固定列。第三种数据更新后某些列隐藏或宽度变化。比如你根据筛选条件动态切换列让某一列从 100 变成 200但固定列区域还停留在旧的宽度计算里就会看到左侧固定区和主体区之间出现一条缝隙或重叠。3.3 宽度给的“刚刚好”和“过于精确”都会出问题固定列宽度不要给小数。有些设计稿上写了width: 100.5px表格渲染时每列都会产生不同程度的四舍五入固定列和主体列的小数累积导致一像素缝隙。这种缝隙在 Retina 屏幕上看不太出来但在普通显示器上就会变成一道刺眼的线。用百分比宽度加固定列是最容易翻车的组合。当某个 Column 设置width: 30%时浏览器计算出的实际像素宽度是不确定的和另一侧固定列用固定像素算出来的宽度很难恰好匹配。建议固定列一律用固定像素值主体列可以混合使用但不要把百分比宽度直接放在固定列上。3.4 一套可复用的排查步骤遇到固定列错位我基本按这个顺序来节省了很多时间先看控制台有没有报错。如果 Column 的 prop 或 slot 名写错表格会少渲染一列固定列区域和主体区域自然对不上。调用一次doLayout()。错位立刻恢复说明是渲染时机问题接下来去查容器动画和显示时机。在nextTick里手动调用一次。如果恢复正常考虑在合适的事件钩子里补触发。检查所有固定列的宽度值是否为整数。有小数先改成整数。检查表格外层容器是否有元素在初始化后被插入比如某个兄弟节点撑宽了页面导致表格实际宽度被撑开。这套流程走下来绝大多数错位都能定位到根因。4. 固定列与操作列的“神仙打架”选择框、排序、按钮点击4.1 复选框列固定后的勾选保留问题有人在社区里问过“element plus selection-change 复选框怎么保留勾选”。这个问题的本质是表格数据如果被重新渲染比如筛选后data数组变化勾选状态就会被清空因为多选框组件默认只根据当前行是否为同一对象来判断选中。当这一列固定时问题会进一步放大因为左边固定区域复制了一份多选框而主体区域又有一份两份多选框的状态必须保持同步。解决方案是在表格上维护一个独立的selectedRows数组手动绑定selectable和 check 相关事件在数据变化时给每行加一个唯一标识做比对el-table :datatableData reftableRef row-keyid selection-changehandleSelectionChange el-table-column typeselection fixedleft reserve-selection width50 / /el-table关键点是reserve-selection。在 Element Plus 里多选列如果设置了reserve-selection并且表格有row-key数据更新后之前勾选的行会被保留不会因为data数组变化而清空。如果你还在用 Element UI 的老版本建议升级或者自己维护一份 id 列表来恢复勾选状态。4.2 固定列里的按钮为什么偶尔点不中操作列固定在右侧后如果里面放的按钮点击命中率很低或者必须点偏一点才能触发那十有八九是固定列的高度和主体列的高度不一致导致按钮实际渲染的位置和视觉位置错位了一两个像素。这种情况在表格行高度不固定、内容自动换行时特别常见。排查方法是用开发者工具选中固定区域内的按钮看它的实际坐标盒子和鼠标落点是否重合。如果确实存在偏差解决办法是给被固定列中的内容加一个最小行高或者统一设置表格的row-height。尽量避免在固定列中使用会导致行高度变化的复杂插槽内容。4.3 排序与固定列叠加时的白屏问题给固定列加sortable后点击排序表格经常会出现一阵子空白或者固定区闪烁。这是因为排序会触发数据重排而固定列区域的数据也会重新复制重排过程中两套区域的更新顺序不同步。我的做法是排序事件里这样做el-table :datatableData sort-changehandleSortChange reftableRef /el-table然后在handleSortChange里手动调用this.$nextTick(() this.$refs.tableRef.doLayout())强制重新布局。如果页面里表格数量多、性能还不错的情况下这样处理几乎无感知。4.4 合并单元格与固定列为什么水火不容Element 的span-method合并单元格功能在存在固定列的时候会表现得非常诡异合并后固定列的行高度和主体区对不上固定列的边框也会断掉。本质原因是合并单元格是通过给单元格设置rowspan和colspan来改占位但固定列区域的 DOM 是克隆出来的它对合并后的高度感知并不敏锐。如果业务必须同时用固定列和合并单元格我一般会放弃组件自带的固定列改成用CSS sticky自己固定那一列这样所有单元格都在同一个表格里合并逻辑不会冲突。这部分我放在最后单独说。5. 多级表头、动态列、大屏场景下的固定列处理5.1 多级表头 固定列布局基准在哪里多级表头就是用了嵌套el-table-column比如一个“时间”列下面再拆“开始时间”和“结束时间”。这种表头一旦和固定列组合错位概率会剧增因为固定区域要复制整个多级表头结构嵌套层级一旦不完整某一级少包了一层宽度就全乱了。在写多级表头时我总结出一个硬性要求固定区域的列嵌套层级必须和主体区域完全一致。你不能在主体区域里“时间”下挂三列而在固定区域里只挂了“时间”自身。组件内部不会帮你做这种容错少一层多一层都会导致宽度错位。如果是左侧固定两列且这两列分属不同层级调试起来非常痛苦。我的建议是多级表头场景下固定列尽量的放在最外层同级的列上不要放在被拆分的子列上。5.2 动态列下固定列刷新不及时动态列是指通过一个columns配置数组循环渲染列。比如根据用户权限切换显示哪些列或者根据分辨率自动隐藏一些列。列变化后固定列区域经常不刷新会导致多出来一串空白或者列不显示。我的处理方式是给表格加一个动态key列配置变化时强制重新渲染表格el-table :keytableKey :datatableData /el-table切换列的地方执行this.tableKey 1; const nextTick this.$nextTick(() { this.$refs.tableRef.doLayout(); });这样虽然丢失了表格内部的局部状态但能保证固定列区域和主体区域在列结构变化之后重新计算一次宽度。对大多数动态列场景来说这个性能开销可以接受。5.3 大屏表格的炫酷改造斑马纹、暗色主题和固定列网上流传的“Vue2 修改一个炫酷的大屏 element 表格”这类效果通常是把表格背景改成深色斑马纹换亮色表头做渐变分割线用半透明。这些样式改动本身不涉及固定列但暗色主题下固定列的边框和阴影问题会被放大。固定列右侧默认会有一道渐变阴影深色背景下那团黑灰色渐变特别显眼。所以在做深色大屏时我一般直接把默认固定列阴影禁掉自己用一层rgba(255, 255, 255, 0.06)的右边框替代观感会干净很多。暗色主题下还有一个容易忽略的坑.el-table__fixed容器默认背景是继承表格背景色的如果你只给主体表格设置了透明背景固定列区域会变成一块白色矩形贴在左侧。必须给.el-table__fixed也设置同样的透明背景左右两侧才不会出现色块。5.4 关于表格整体居中 CSS 的补充关于“让 table 水平居中 css”这个问题纯 HTML table 可以用margin: 0 auto但 Element 的表格外层是一个宽度计算过的容器直接用margin: 0 auto会让固定列的计算基准跑偏因为容器本身宽度不再等于视口宽度。更稳的做法是给表格外层套一个固定宽度的容器让这个容器margin: 0 auto内部 Element 表格始终保持 100% 宽度。这样固定列计算的参考宽度就是容器宽度不会受页面居中布局的影响。6. 我的建议什么时候用 fixed什么时候用 CSS sticky 收尾6.1 CSS sticky 是固定列的一个更轻替代如果你项目的 Element 表格版本较老或者表格里同时有合并单元格、展开行、动态列这些复杂功能组件自带 fixed 会反复和你作对。这时候用 CSSposition: sticky自己固定列反而是更可控的方案。核心写法是这样的.sticky-col { position: sticky; left: 0; z-index: 10; background: #fff; }在el-table里给对应列加一个 class但注意要同时处理表头和表体的两行而且要确保这一列背景色不透底否则滚动时后面的内容会透过文字看到。sticky 方案最大的优点是不需要复制 DOM不用和 Element 内部的 fixed 逻辑打交道永远不存在错位问题。缺点是你要自己处理边框、阴影、层级而且sticky只在支持该属性的浏览器里生效。中后台系统如果浏览器环境可控我建议优先考虑 sticky 方案。6.2 固定列数量不是越多越好我见过有人把表格左边固定三列右边固定三列滚动区域缩到只剩中间一小块。这种布局看似方便实际上中台表格的内容区被大幅度压缩用户看主要数据反而要频繁上下查找体验更差。固定列应该只服务于“高频操作”和“关键识别字段”。我的经验是左侧最多固定两列右侧最多固定一列。超出这个范围就应该回头审视你的表格设计是不是字段顺序本身就设计得不合理。6.3 最后的个人经验固定列问题排查到后面你会发现大部分都不是 Element 的 bug而是表格渲染时机、容器宽度、列配置这几者之间的协同问题。遇到问题先别急着升级版本或者改源码先按照“doLayout 能否恢复 → 找触发时机 → 检查列宽配置 → 检查容器环境”的顺序走一遍通常都能找到原因。如果你在不同项目里反复踩固定列的坑我的建议是把排查步骤沉淀成文档团队新人遇到问题时可以直接套用。我在团队里就整理了一份固定列问题定位清单新人照着操作大部分问题可以自己解决不用再来回问这比任何炫酷的封装都实用。
返回列表