兼容两种Table Layout算法的表格列宽分配算法
1. table-layout 布局算法介绍
(1) auto
表格及单元格的宽度取决于其包含的内容。大多数浏览器采用自动表格布局算法对表格布局。
(2) fixed
- 表格和列的宽度通过表格的宽度来设置,某一列的宽度仅由该列首行的单元格决定
- 整个表格可以在其首行被下载后就被解析和渲染
- 对于“automatic”自动布局方式来说可以加速渲染,但是其后的单元格内容并不会自适应当前列宽
- 任何一个包含溢出内容的单元格可以使用
overflow
属性控制是否允许内容溢出
2. auto模式下的表格列宽调整逻辑
自动宽度布局,速度上虽然不如前者,但它是表格的默认宽度布局方案。慢的原因在于表格会对每一个单元格宽度进行计算,每个单元格的宽度由内容决定。
宽度计算规则:
- 获取表格中每个column的宽度,column的宽度由每个单元格宽度决定,每个单元格宽度又由单元格内的内容决定,计算每行某列的单元格,并获取某列单元格的最小值和最大值。
- 设表格的宽度为width,所有列的宽度和为AllcolWidth,遍历每一行。
若width为auto,则表格宽度就是由所有列总和值决定,每列的宽度由自身的内容决定。width = AllcolWidth
- 若每列的宽度为auto,每列的宽度由内容决定,计算出每行每列宽度的最大值和最小值,某列的宽度由某行的最大值决定。
- 若部分列宽度为固定值,AllcolWidth = 未固定列宽为某行的最大值 + 所设置的宽度
- 若每列都有固定的宽度值,表格的宽度即为 AllcolWidth = 各固定值之和
若width为100%(宽度为父元素的值)或为固定值
- 若每列的宽度为auto,每列的宽度由内容决定,计算出每行每列宽度的最大值和最小值,根据这些最大值和最小值计算列宽。
- 若部分列宽度为固定值,AllcolWidth = 未固定列宽自适应计算的宽度(在最大值和最小值之间) + 所设置的宽度
- 若每列都有固定的宽度值,需要计算出column宽度、border与单元格间距的总和为AllColWidth
- width > AllColWidth,多余的宽度会均分给所有列。
- width < AllColWidth,每个column,单元格进行width/AllColWidth比例的缩小。
3. fixed模式下的表格列宽调整逻辑
固宽布局必须要设置:width: 100% 或 width: 固定值
根据表格的宽度 width 和第一行所有列宽的总和 colWidth ,也就是说表格在第一行被下载后就被解析和渲染,后面行的列宽以第一行为准。
宽度计算规则:第一行的所有列宽由<colgroup>中的<col>或单元格的 width 属性决定。
- 其他列的宽度均为auto,将根据列数平均分配,各列宽度相同
- 表格中某几列宽度设置了固定宽度,这几列宽度总和为partColWidth,其它列将均分剩余宽度,公式为(table-width – partColWidth)/ 剩下的列数
- 若第一行所有列的宽度都为固定,所有列宽度总和设为ColWidth,表格宽度为width。
若ColWidth >= width,则表格宽度为ColWidth,所有的列的宽度为自己设置的宽度;
若ColWidth < width,则表格宽度为width,所有列的宽度将重新按比例分配宽度,使更新后的colWidth与width相等,即 (自己设置的宽度 / ColWidth) * width - 若有列合并,根据以上规则得出此列的宽度,合并列里面的列平分合并后的宽度。
对于td单元格,当table设置 white-space: nowarp; 是,fixed模式会有内容溢出的问题,直接可以使用overflow: hidden 隐藏溢出内容。或者使用 word-break: break-all; word-wrap: break-word; 强制换行。如果想查看单元格里面的内容,overflow: scroll,可在单元格里面滚动查看内容。
4. 兼容table-layout的表格列宽分配算法
const recalculateColWidth = (
columns: BaseTableCol[],
thWidthList: { [colKey: string]: number },
tableLayout: string,
tableElmWidth: number,
): void => {
let actualWidth = 0;
const missingWidthCols: BaseTableCol[] = [];
const thMap: { [colKey: string]: number } = {};
// 计算现有列的列宽总和
columns.forEach((col) => {
if (!thWidthList[col.colKey]) {
thMap[col.colKey] = isNumber(col.width) ? col.width : parseFloat(col.width);
} else {
thMap[col.colKey] = thWidthList[col.colKey];
}
const originWidth = thMap[col.colKey];
if (originWidth) {
actualWidth += originWidth;
} else {
missingWidthCols.push(col);
}
});
let tableWidth = tableElmWidth;
let needUpdate = false;
// 表宽没有初始化时,默认给没有指定列宽的列指定宽度为100px
if (tableWidth > 0) {
// 存在没有指定列宽的列
if (missingWidthCols.length) {
// 当前列宽总宽度小于表宽,将剩余宽度平均分配给未指定宽度的列
if (actualWidth < tableWidth) {
const widthDiff = tableWidth - actualWidth;
const avgWidth = widthDiff / missingWidthCols.length;
missingWidthCols.forEach((col) => {
thMap[col.colKey] = avgWidth;
});
} else if (tableLayout === 'fixed') {
// 当前列表总宽度大于等于表宽,且当前排版模式为fixed,默认填充100px
missingWidthCols.forEach((col) => {
const originWidth = thMap[col.colKey] || 100;
thMap[col.colKey] = isNumber(originWidth) ? originWidth : parseFloat(originWidth);
});
} else {
// 当前列表总宽度大于等于表宽,且当前排版模式为auto,默认填充100px,然后按比例重新分配各列宽度
const extraWidth = missingWidthCols.length * 100;
const totalWidth = extraWidth + actualWidth;
columns.forEach((col) => {
if (!thMap[col.colKey]) {
thMap[col.colKey] = (100 / totalWidth) * tableWidth;
} else {
thMap[col.colKey] = (thMap[col.colKey] / totalWidth) * tableWidth;
}
});
}
needUpdate = true;
} else {
// 所有列都已经指定宽度
if (notCalculateWidthCols.value.length) {
// 存在不允许重新计算宽度的列(一般是resize后的两列),这些列不参与后续计算
let sum = 0;
notCalculateWidthCols.value.forEach((colKey) => {
sum += thMap[colKey];
});
actualWidth -= sum;
tableWidth -= sum;
}
// 重新计算其他列的宽度,按表格剩余宽度进行按比例分配
if (actualWidth !== tableWidth || notCalculateWidthCols.value.length) {
columns.forEach((col) => {
if (notCalculateWidthCols.value.includes(col.colKey)) return;
thMap[col.colKey] = (thMap[col.colKey] / actualWidth) * tableWidth;
});
needUpdate = true;
}
}
} else {
// 表格宽度未初始化,默认填充100px
missingWidthCols.forEach((col) => {
const originWidth = thMap[col.colKey] || 100;
thMap[col.colKey] = isNumber(originWidth) ? originWidth : parseFloat(originWidth);
});
needUpdate = true;
}
// 更新列存储值
updateThWidthList(thMap);
if (notCalculateWidthCols.value.length) {
notCalculateWidthCols.value = [];
}
};
当前算法已在 Tencent/tdesign-vue 仓库中实现,具体效果可以参考 TDesign 官方示例。
5. 参考文章
[1] https://css-tricks.com/fixing-tables-long-strings/
[2] https://x-front-team.github.io/2017/04/25/table-layout%E7%90%86%E8%A7%A3%E5%88%B0%E6%94%BE%E5%BC%83/