前言
对于动态获取数据的表格,如果期望单元格内容不折行,就要设定足够的宽度,同时又希望表格内容尽量紧凑,但是,由于数据不确定,所以无法预设宽度,怎么办呢?有这样一种办法:
方案
- 先让表格渲染
- 引入CSS class来让单元格内容单行显示
- 遍历表体的.cell,算出每个元素的
scrollWidth
,汇总到二维数组里 - 考察二维数组的每个一级元素的每个单元格的宽度,找出最大值
-
<el-table-column>
设置的width
都等于各自列的最大值即可
这个方案按理说,会发生表格闪烁,因为先渲染,又调整列宽的缘故。为了解决这个问题,我想到了visibility: hidden;
,当hidden
时,表格依然会渲染,只不过不显示,此时就可以计算各种宽度,等计算好,赋值好,再visible
即可。
恰好可以借用loading
变量实现hidden
和visible
的切换。
template
- 给表格组件加上这个:
ref="listTable" class="columns-fit" :class="loading ? null : 'visible'"
- 给每个
<el-table-column>
加上width
,如下:
<el-table-column type="selection" align="center" :width="colWidthList[0]" />
<el-table-column label="序号" align="center" prop="id" :width="colWidthList[1]" />
...
...
其中下标表示第几个列。你可以给有些列不设width
,或者设置固定数值,此时其他列的下标无需调整,但是如果列顺序变了,下标必须重排。
style
在某个全局引入的scss文件写入:
.columns-fit {
.el-table__header-wrapper, .el-table__body-wrapper {
visibility: hidden;
}
&.visible {
.el-table__header-wrapper, .el-table__body-wrapper {
visibility: visible;
}
}
.el-table__body-wrapper {
overflow: auto;
}
td>.cell {
display: inline-block;
white-space: nowrap;
width: auto;
overflow: auto;
}
}
script
先不说本方案,先说原始的请求数据列表的代码大致是这样:
getList() {
this.loading = true;
list(this.queryParams).then(response => {
this.list = response.data;
this.total = response.total;
this.loading = false;
});
},
然后我们对它略改造,加上一句:
getList() {
this.loading = true;
list(this.queryParams).then(response => {
this.list = response.data;
this.total = response.total;
this.$nextTick(() => {
setTimeout(() => {
this.colWidthList = this.$adjustColumnWidth(this.$refs['listTable'].$el);
this.loading = false;
});
});
});
},
colWidthList
去data
里定义一个空数组,用来存每个col的最终宽度。
比较迷的是setTimeout
,你是不是不知道我为啥加一句这个?当表格没有横向滚动条,用下方的代码计算出来的每个单元格的scrollWidth
会有错误,原因是Element UI的某些计算规则比较迷,它先计算一遍,然后微调一遍,会让单元格稍微变化几像素,而setTimeout
从JS底层说是宏任务,可以等待Element UI对单元格的调整结束,这样,得到的scrollWidth
才是准的。
adjustColumnWidth
函数需要写入一个全局JS:
export default function(el) {
let widthList = [];
el.querySelectorAll('.el-table__body tr').forEach((tr) => {
tr.querySelectorAll('td').forEach((td, i) => {
if (!widthList[i]) {
widthList[i] = [];
}
widthList[i].push(td.scrollWidth);
});
});
return widthList.map(width => Math.max(...width));
}
main.js里引入:
import adjustColumnWidth from '@/utils/adjustColumnWidth';
Vue.prototype.$adjustColumnWidth = adjustColumnWidth;
到此OK。
使用特别说明
1. 本方案不考虑表头的单元格溢出,请另外考虑
Element UI里面有这样的样式,其中由于text-overflow
的值没有none
,也就是说,text-overflow
一旦写上了就无法取消,导致表头无法像表身一样呈现单行且无省略号的状态,因此也就无法取得我们想要的表头单元格的scrollWidth
,所以,本方案不计算表头单元格的宽度。这就导致了一个问题:
如果某列的表头字符很长,但表身内容很短,这样计算得到的width
会比表头字符还要短,表头会出现折行。
结论:要么,你就接受这种折行的设定,要么,就给col写死固定的、足够的width
值。
2. 删除列、调整列顺序时,:width="colWidthList[n]"
的下标要记得对应修改
请记得对应修改。下标应该永远是列的排序序号。
3. 浏览器窗口由小窗拉大到大窗,表格宽度不变,右侧出现空白,怎么解决?
首先说,width
属性是Element UI官方属性,如果全部列都设置了width
,那么官方也没有办法让表格自适应容器宽度,所以这其实并不是本方案的锅。
我这里提个解决方案:
监听window.onsize
,动态修改colWidthList
,等比放大,代码我就不写了,因为本身这个需求就是极小概率出现的需求。
4. 不要给所有表格都用本方案
如果表格明显内容稀松,就坚决不要使用本方案,因为没必要。
5. 不要给所有列都用本方案
假如某列的内容忽长忽短,短的只有几个字,长的有50个字,那么这一列显然不适合使用自适应列宽,因为会造成大面积的空白,请给该列锁定width
。
6. 本方案的缺点
本方案为了不让表格抖动,造成了2个负面效果:
会让表格消失几十到几百毫秒甚至几秒,根据ajax请求速度而定。如果不采用本方案,表格只是被loading遮罩遮住几十到几百毫秒至几秒,而且遮罩往往半透明,能看到表格的隐约内容,是一种具有“高级感”的设计。
设置
width
会导致大规模的UI回流和重绘,页面会非常轻微、不易觉察的卡顿一下,不过好在Element UI做政企系统多,可以强迫用户使用现代浏览器,所以问题很轻微。
总之,如果需求方对界面美观比较在意,对轻微的、不易察觉的卡顿不太在乎,那么可以考虑本方案,如果追求极致流畅,则请不要使用本方案。