最近项目上线以后,产品对首页的列表提出了列宽自适应内容的需求,即列的宽度由当前列中内容最宽的一个单元格决定(单元格内容不换行)。项目中的表格使用的是element-ui的table组件,很是强大,知足了各类各样的需求,例如固定列,固定表头,展开行等等。可是面对这样的需求,貌似并无直接提供配置项支持。css
最先接到这个需求的第一反应是想到element文档中提到的fit属性。在仔细查阅了文档以后,发现这个属性是默认开启的,实际上fit的准确含义是全部列撑满整个表格所在的容器。fit属性的相关源码以下:git
//没有指定宽度的列
let flexColumns = flattenColumns.filter((column) => typeof column.width !== 'number');
const fit = this.fit;
const bodyWidth = this.table.$el.clientWidth;
let bodyMinWidth = 0;
if (flexColumns.length > 0 && fit) {
//获取表格的最小宽度
flattenColumns.forEach((column) => {
bodyMinWidth += column.width || column.minWidth || 80;
});
const scrollYWidth = this.scrollY ? this.gutterWidth : 0;
//若是最小宽度小于容器宽度,即没有撑满容器
if (bodyMinWidth <= bodyWidth - scrollYWidth) {
this.scrollX = false;
const totalFlexWidth = bodyWidth - scrollYWidth - bodyMinWidth;
//把富余的宽度均分给除第一列的其余列,剩下来的给第一列(避免宽度均分的时候除不尽)
if (flexColumns.length === 1) {
flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth;
} else {
const allColumnsWidth = flexColumns.reduce((prev, column) => prev + (column.minWidth || 80), 0);
const flexWidthPerPixel = totalFlexWidth / allColumnsWidth;
let noneFirstWidth = 0;
flexColumns.forEach((column, index) => {
if (index === 0) return;
const flexWidth = Math.floor((column.minWidth || 80) * flexWidthPerPixel);
noneFirstWidth += flexWidth;
column.realWidth = (column.minWidth || 80) + flexWidth;
});
flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth - noneFirstWidth;
}
}else{
//fit=false,对没有设置宽度的列宽度设为80
flattenColumns.forEach((column) => {
if (!column.width && !column.minWidth) {
column.realWidth = 80;
} else {
column.realWidth = column.width || column.minWidth;
}
bodyMinWidth += column.realWidth;
});
}
复制代码
从源码上可见这个属性并非咱们想要的列宽自适应内容,这段代码也是updateColumnsWidth的主要逻辑。总结一下,列的宽度和内容在el-table内部并无联系。所以,想要列宽自适应内容只能从外部着手。github
既然el-table没有配置项支持,那可否从css角度解决呢?我联想到原生table就有这样的功能,简单介绍下原生table的布局模式。web
table-layout CSS属性定义了用于布局表格单元格,行和列的算法算法
auto 大多数浏览器采用自动表格布局算法对表格布局。表格及单元格的宽度取决于其包含的内容。element-ui
fixed 表格和列的宽度经过表格的宽度来设置,某一列的宽度仅由该列首行的单元格决定。在当前列中,该单元格所在行以后的行并不会影响整个列宽。 使用 “fixed” 布局方式时,整个表格能够在其首行被下载后就被解析和渲染。这样对于 “automatic” 自动布局方式来讲能够加速渲染,可是其后的单元格内容并不会自适应当前列宽。任何一个包含溢出内容的单元格可使用 overflow 属性控制是否容许内容溢出。浏览器
el-table采用的就是第二种table-layout:fixed
模式,可否经过改变table-layout为auto来实现呢?尝试以后发现是行不通的。一个缘由在于,el-table的固定表头和固定列都须要生成额外的table来实现。这样一来控制多个table之间的列宽同步就成了问题。而解决这个问题的原理就是:为 <table>
增长 <col>
元素,在 <col>
元素上设置 width 属性,动态的同步表格头和表格内容的<col>
元素上的宽度设置。这个方案偏偏是创建在table-layout:fixed
的前提之下的。bash
若是你项目中对表格的需求仅停留在展现,实际上是能够考虑使用原生的table的,使用非fixed模式的table很是简单的实现列宽自适应内容的需求。(产品提出该需求参考的头条广告后台,就是使用的原生table,可是也有不少缺点)字体
'自动'的路子看来是行不通了,那只能在渲染以前手动算出列宽了。对于内容是文本的列,这么作是能够的。把文本的字符类型分为三种:中英文,数字,特殊字符,经过正则筛选出来。再对每一个类型的字符乘以字体大小再折合必定的比例,就获得了大体的宽度。循环整组表格数据获得须要自适应内容的列宽的最大值,取其和表头宽度之间的最大值便可。
getMaxLength (arr) {
return arr.reduce((acc, item) => {
if (item) {
const str = item.toString()
const char = str.match(/[\u2E80-\u9FFF]/g)
const charLen = char ? char.length : 0
const num = str.match(/\d|\./g)
const numLen = num ? num.length : 0
const otherLen = str.length - charLength - numLength
let calcLen = charLen * 1.1 + numLen * 0.65 + otherLen * 0.5
if (acc < calcLen) {
acc = calcLen
}
}
return acc
}, 0)
}
复制代码
即便是对于文本类型的列而言,上述计算的获得的宽度也并不是精准的宽度。由于字体分为等宽字体和比例字体,web上大部分的中文字体都是等宽字体,好比用的最多的微软雅黑。可是英文字体基本都是比例字体了,每一个字母的宽度是不一样的。
因为目前项目中的自定义列宽度都是能够预先知道的,例如开关,下拉选择框等。因此对文本类型的列作一个手动宽度计算已经基本能够知足产品提出的需求。研究需求的过程当中,在网上找到一个开源的列宽自适应组件,也是基于el-table的二次封装,若是各位想直接拿来用的话能够参考。
勉强算是解决了问题,可是显然不是一个完善的方案,若是各位有好的建议或者实现,欢迎评论告知。