最近項目上線以後,產品對首頁的列表提出了列寬自適應內容的需求,即列的寬度由當前列中內容最寬的一個單元格決定(單元格內容不換行)。項目中的表格使用的是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的二次封裝,若是各位想直接拿來用的話能夠參考。
勉強算是解決了問題,可是顯然不是一個完善的方案,若是各位有好的建議或者實現,歡迎評論告知。