笔记:表格宽度的计算规则

自从 2020 年下半年我在千聊做 B 端设计师开始,我就开始了与 Table 组件打交道。从那时候我就发现了,表格的布局,好像永远都跟我的设想不符,也很难判断到造成这个后果的原因是我提出的设计需求太奇葩,还是前端老哥压根没按我说的去还原。

自从 2020 年下半年我在千聊做 B 端设计师开始,我就开始了与 Table 组件打交道。从那时候我就发现了,表格的布局,好像永远都跟我的设想不符,也很难判断到造成这个后果的原因是我提出的设计需求太奇葩,还是前端老哥压根没按我说的去还原。

截图列出常见的表格问题

直到 2021 年,我加入了欢聚集团,一样是做 B 端设计师,一样要跟 Table 组件打交道。但在这里做设计的难度更大,要考虑到更多的场景。

所以我觉得不能再逃避了,自己真的要花点时间搞懂 Table 的列宽计算规则。于是我花了大概2~3周的工作之外的时间,去研究 Table 组件的布局规则,去做实践验证。

下文就是我这段时间的知识结晶,从 UI 设计师的角度,阐述对 Table 布局的理解。对我来说,把研究到的东西写成文档,能再次全面地梳理和复习一遍;对读者来说,希望…有少许帮助吧~

必要的概念说明

掌握表格相关的名词释义,才能理解表格的形成。在设计中我们需要时刻谨记,表格的宽度是不定的,内容宽度也是不定的,浏览器最终渲染出来的真实列宽,是多个属性共同作用下的结果。

表格宽度 (tableWidth):

table 的 width 属性。可以是自动,也可以是某个百分比或特定的值。自动宽度时,表格宽度会根据内容来改变。以下是自动宽度的三种情况:

  • auto 或不设置时,表格宽度会撑满容器(默认值);
  • min-content 时,表格宽度是由最小单元格内容宽度的列组成;
  • max-content 时,表格宽度是由最大单元格内容宽度的列组成;
以上三种情况的展示。有父容器,表格,和三种情况的内容。

当设置了自动宽度的表格,宽度到达父容器的宽度,效果就跟固定表格宽度一致了。所以自动宽度的表格, 其真实表格宽度可能是又各列撑起的宽度,也可能是父容器的宽度。

定宽列的宽度 (fixedWidth):

某列被设置的宽度,可以理解为我们平时让前端限制某列的列宽多少px,指的就是该项的值。

值得一提的是,定宽列的宽度 (fixedWidth) 会受到两个宽度的影响:列宽度 (colWidth) 和单元格宽度 (cellWidth)。不同的布局方式,有不同的影响。具体在后文会在后文写清楚。

列宽度 (colWidth):

col 标签上设置的 width 属性。也就是通过 Table 组件的 API width 所设置的宽度。

单元格宽度 (cellWidth):

td、th 内设置的 width 属性,也就是单元格里 div 或图片等撑起来的宽度。取较大的值。

真实列宽 (realColWidth):

通过计算后,真实展现在界面中的列宽度。也就是这篇笔记我们所探讨的内容。

单元格内容宽度 (contentWidth):

单元格里面的内容所占的宽度,单元格内容可以是文本、图片或 div 等组成。如果是文本,单元格内容宽度是这段文本排成一行所占的宽度;如果是图片,则是该图片渲染出来的真实尺寸;如果是 div,则宽度与 div 的宽度一致。

最小单元格内容宽度 (minContentWidth):

内容可以被压缩至最小的宽度。如果内容是一段中文,则该值为一个汉字的宽度;如果是一段英文,则该值为这段英文里,宽度最大的字母所占的空间;如果是 div,则该值为 div 的宽度。

值得一提的是,文本的换行规则,容器的 Padding 等也会影响到该宽度的值,下图会帮助大家理解最小单元格内容宽度

最小内容宽度的计算

两种布局方式

table-layout 属性定义了用于布局表格单元格,行和列的算法。该属性可被配置为:

  • table-layout: auto;( 是 Ant Design / Arco Design / TDesign 的 Table 组件,以及 HTML <table> 的默认值)
  • table-layout: fixed; ( 是 Element UI 的 Table 组件默认值)

两种布局方式的不同之处:

  • auto 布局下,表格和列的宽度,会受到内容宽度的影响。比如第一列的文本比第二列的长,那么浏览器就会根据内容长度来分配列宽。
  • fixed 布局下,表格和列的宽度,通过列宽度 (colWidth) 来设置,某一列的宽度仅由该列首行的单元格决定,所以内容不会影响列宽。用该方式布局可以使表格的渲染速度更快,因为它只需要加载第一行的内容,就能渲染出表格的宽度、布局。

两者之间的区别,也可以透过下图来更清楚地认识。

auto 布局和 fixed 布局的区别

好了,接下来我们正式进入列宽计算的世界吧~

auto 布局的列宽计算

在 table-layout: auto; 的布局下,影响真实列宽的有以下三种宽度:

  • 定宽列的宽度 (fixedWidth)
  • 单元格内容宽度 (contentWidth)
  • 表格宽度 (tableWidth)

类型1:表格中所有列都设置宽度

场景A:表格宽度足够放下所有列时:

此时表格的真实列宽,会根据每列所设定的宽度来按比例分配。

// 定宽列的宽度
fixedWidth = Math.max(colWidth, cellWidth)
// 真实列宽
realColWidth = Math.max(fixedWidth / sum(fixedWidth) * tableWidth, minContentWidth)
// 真实表格宽度
realTableWidth = Math.max(tableWidth, sum(realColWidth))

场景B:表格宽度不足以放下所有列时:

真实表格宽度会超出容器,出现横向滚动条(如有配置)。此时列宽和表格宽度的计算公式:

// 定宽列的宽度
fixedWidth = Math.max(colWidth, cellWidth)
// 真实列宽
realColWidth = Math.max(fixedWidth, minContentWidth)
// 真实表格宽度
realTableWidth = sum(realColWidth)

上述两种场景的具体表现, 如下图所示:

auto 的类型1

类型2:表格中所有列都不设置宽度

场景A:表格宽度足够放下所有列时:

根据每列的最大内容宽度,来按比例分配列宽。如果单元格内文本没有触发换行,那么此时列宽和表格宽度的计算公式:

// 不定宽列的宽度
autoWidth = Math.max(contentWidth)
// 真实列宽
realColWidth = Math.max(autoWidth / sum(autoWidth) * tableWidth, minContentWidth)
// 真实表格宽度
realTableWidth = Math.max(tableWidth, sum(realColWidth))

场景B:表格宽度不足以放下所有列时:

每一列都会被压缩至最小内容宽度,并且真实表格宽度会超出容器,出现横向滚动条(如有配置)。此时列宽和表格宽度的计算公式:

// 不定宽列的宽度
autoWidth = minContentWidth
// 真实列宽
realColWidth = autoWidth
// 真实表格宽度
realTableWidth = sum(realColWidth)

上述两种场景的具体表现, 如下图所示:

auto 的类型2

注:如果实际情况是处于场景A场景B之间,即单元格里的文本因为宽度挤压而出现换行,却又没被挤压到内容最小宽度时。我这还暂时没有发现到一条公式可以计算具体的宽度。换言之,上面的公式只对未出现文本换行,或所有列都被挤压到最小宽度时的表格有效。

类型3:部分列设置宽度,部分列不设置宽度

场景A:所有不定宽列的最小单元格内容宽度之和小于或等于减去定宽列之后的表格宽度

不定宽列会按照单元格内容宽度,按比例分配减去定宽列之后的表格宽度。这意味着:

  • 不定宽列的真实列宽会被按比例压缩或延长;
  • 定宽列的真实列宽,则按所设置的宽度来展示。

此时列宽和表格宽度的计算公式:

// 定宽列的宽度
fixedWidth = Math.max(colWidth, cellWidth)
// 不定宽列的宽度
autoWidth = Math.max(contentWidth)
// 定宽列的真实列宽
realColFixedWidth = Math.max(fixedWidth, minContentWidth)
// 不定宽列的真实列宽
realColAutoWidth = Math.max(autoWidth / sum(autoWidth) * (tableWidth - sum(realColFixedWidth)), minContentWidth)
// 真实表格宽度
realTableWidth = Math.max(tableWidth, sum(realColFixedWidth) + sum(realColAutoWidth))

帮助理解:类型3-场景A里,计算不定宽列的真实列宽,可以把它当做类型2-场景A 来思考,只是表格宽度是不包含定宽列的真实列宽。

场景B:所有不定宽列的最小单元格内容宽度之和大于减去定宽列之后的表格宽度

这时不定宽的列已经被压缩到无法再压缩,只能压缩定宽的列。这意味着:

  • 不定宽列的内容会以最小内容宽度来展示;
  • 定宽列,根据所设定的宽度和总定宽列的宽度,按比例压缩展示。

此时列宽和表格宽度的计算公式:

// 定宽列的宽度
fixedWidth = Math.max(colWidth, cellWidth)
// 不定宽列的宽度
autoWidth = Math.max(contentWidth)
realColFixedWidth = Math.max(fixedWidth / sum(fixedWidth) * (tableWidth - sum(realColAutoWidth)), minContentWidth)
// 不定宽列的宽度
realColAutoWidth = minContentWidth
// 真实表格宽度
realTableWidth = Math.max(tableWidth, sum(realColFixedWidth) + sum(realColAutoWidth))

帮助理解:类型3-场景B里,计算定宽列的真实列宽,可以把它当做类型2-场景B 来思考,只是表格宽度是不包含不定宽列的真实列宽。

上述两种场景的具体表现, 如下图所示:

auto 的类型3

fixed 布局的列宽计算

在 table-layout: fixed 布局下,单元格的内容宽度,不会对表格的真实列宽产生影响。这时影响真实列宽的有以下三种宽度:

  • 列宽度 (colWidth)
  • 单元格宽度 (cellWidth)
  • 表格宽度 (tableWidth)

细心的网友可能发现了,colWidth 和 cellWidth 之间较大的值,就是 auto 布局里 fixedWidth 值。但在 fixed 模式中,如果某一列同时设置了 colWidth、cellWidth,优先级是 colWidth > cellWidth。

即在 fixed 布局下,通过 API width 所设置的列宽,是比在 td、th 或 div 里设置的宽度,权重要高,不需要比较两者之间的大小。

类型1:表格中所有列都设置宽度

场景A:表格宽度足够放下所有列时:

此时表格的真实列宽,会根据每列所设定的宽度来按比例分配。计算公式如下:

// 定宽列的宽度
fixedWidth = colWidth || cellWidth
// 真实列宽
realColWidth = Math.max(fixedWidth / sum(fixedWidth) * tableWidth, fixedWidth)
// 真实表格宽度
realTableWidth = tableWidth

场景B:表格宽度不足以放下所有列时:

此时表格的真实列宽,与每列所设定的宽度一致。真实表格宽度会超出容器,出现横向滚动条(如有配置)。计算公式如下:

// 定宽列的宽度
fixedWidth = colWidth || cellWidth
// 真实列宽
realColWidth = fixedWidth
// 真实表格宽度
realTableWidth = sum(fixedWidth)
fixed 的类型1

类型2:表格中所有列都不设置宽度

场景A:表格宽度足够放下所有列时:

所有列会等分表格的宽度。

场景B:表格宽度不足以放下所有列时:

所有列会等分表格的宽度,由于列宽不受内容控制,所以单元格内的内容有可能会溢出到单元格以外的位置。

上述两个场景的计算公式:

// cols = 列数
// 不定宽列的宽度
autoWidth = tableWidth / cols
// 真实列宽
realColWidth = autoWidth
// 真实表格宽度
realTableWidth = tableWidth
fixed 的类型2

类型3:部分列设置宽度,部分列不设置宽度

场景A:表格宽度足够放下所有定宽的列时:

定宽的列,真实列宽等于所设定的宽度值,不定宽的列会平分表格剩余宽度。计算公式如下:

// 定宽列的宽度
fixedWidth = colWidth || cellWidth
// 定宽列的真实列宽
realColFixedWidth = fixedWidth
// autoCols = 不定宽列的列数
// 不定宽列的真实列宽
realColAutoWidth = (tableWidth - sum(realColFixedWidth)) / autoCols
// 真实表格宽度
realTableWidth = tableWidth

场景B:表格宽度不足以放下所有定宽的列时:

真实表格宽度会超出容器,出现横向滚动条(如有配置)。超出表格宽度的列,都会游离在表格组件以外。如果该列是定宽列,则保持原有宽度;如果该列是不定宽列,则单元格被挤压至最小内容宽度。计算公式如下:

// 定宽列的宽度
fixedWidth = colWidth || cellWidth
// 定宽列的真实列宽
realColFixedWidth = fixedWidth
// 不定宽列的真实列宽
realColAutoWidth = 0
// 真实表格宽度
realTableWidth = tableWidth
fixed 的类型3

感想

呜呼!花了大半天时间来列出表格在两种布局,六种类型,十二种场景下的列宽规则,终于把前段时间学在脑子内的知识写成文档了。这篇文章应该是我写过比较硬核的一篇了,通篇都是理论。

重要的是,这些理论知识,应用在 Ant Design、Arco Design、Element UI 等主流的组件库,都是适用的,因为这些表格组件都是在 HTML 的 <table> 基础上完成的。

耐人寻味的一点是,我在2021年年初对 Web 换行规则的探索,来到表格组件上也有发挥的空间,它帮助了我理解 “最小单元格内容宽度” 。所以不得不说,网页设计上的知识,真是环环相扣,让我有源源不断的学习动力。

之后的文章可能会写点实例。比如在不同业务场景下,应该使用哪种布局,或应该怎么给单元格设定规则。毕竟理论知识,需要在实际的业务场景里面验证、总结出实例,才算一个完整的学习过程。

参考文章