引子
网页开发中,经常需要实现等宽列效果,比如 Web App 中的 TabBar、Slider 底部的等宽指示条,而且往往元素的个数是不确定的,无法设定固定的百分比宽度。
虽然借助 flexbox 可以轻松实现:
.container {
display: flex;
}
.item {
flex: 1;
}
但是,你别忘了国情,我朝一些倔强的 UA 并不支持 flexbox,得找祖(qí )传(qiǎo)秘(yín)方(jì)来治这些奇葩。
基于表格布局的实现
好吧,来窥探一下 GitHub 的实现方式。
这个项目状态 GitHub 用户再熟悉不过了,简化后的 HTML 如下:
<ul class="numbers-summary">
<li>1,224 commits</li>
<li>3 branches</li>
<li>19 releases</li>
<li>21 contributors</li>
</ul>
实现等分效果的是下面几行样式:
.numbers-summary {
display: table;
table-layout: fixed;
width: 100%;
}
.numbers-summary li {
display: table-cell;
}
不信?你可以试试看。
其实,之前还有一个更简洁的版本:
.ew-v1 li {
display: table-cell;
width: 1%;
}
是不是有点意思?当年被 DIV + CSS 口诛笔伐的 table
还有这玩法?而 GitHub 实现方式的细微改变又基于何种考虑呢?
其实很多人未曾真正懂 table
,我也算一个。
背后的表格布局算法
带着几分迷惑,Google 一下,首先看到的当然是 GitHub 的例子,背后的原理则在 W3C 规范中有详细描述。
怪我咯?
当年 table 布局被摈弃,除了代码臃肿、维护不便外,还有一个主要的原因是表格渲染时导致页面重绘带来的性能问题。
怪我咯!<table>
哥有点委屈:浏览器让我这么干的,却让我背黑锅。性能低下的根源在于浏览器默认的 table 宽度布局算法。
表格宽度算法
相信很多前端开发者都未留意过 table-layout
这个 CSS 属性,说来惭愧,我也是现在才注意到。
根据 MDN 上的文档:
table-layout
属性定义 table 用于布局单元格、行、列的算法。
table-layout: auto;
table-layout: fixed;
auto
和 fixed
两个值分别对应 Automatic table layout
和 Fixed table layout
两种布局算法。大多数浏览器的初始值为 auto
,即默认使用自动布局算法。
Automatic table layout
自动表格布局算法中,表格及单元格宽度由其包含的内容决定,要在整个表格后加载解析完成之后才能最终确定,如果某行的列宽和前面的不一致,则之前绘制好的行也必须重新绘制,因此很低效。
列宽计算流程如下:
- 计算每个单元格(cell)的「最小内容宽度」(minimum content width, MCW):格式化的内容可以跨越任意行数,但不可以溢出单元格盒子。如果单元格设定的
width
(W) 大于 MCW,则单元格最小宽度为 W;若width
为auto
,则意味着单元格最小宽度为 MCW 。另外,计算每个单元格的最大宽度:不换行地格式化内容,明确有换行符的除外。- 对于每一列(column),从仅跨越该列的单元格确定确定最大和最小宽度。最小宽度为 单元格中「最小单元格宽度」最大者及 column
width
值取其大者。最大宽度为单元格中「最大单元格宽度」最大者及 columnwidth
值取其大者。- 对于跨度超过一列的单元格,增大其跨越的列的最小宽度,使得合计起来至少和单元格一样宽。最大宽度同理。如果可能,使用大概一致的值扩展合并的列。
- 对于每个
width
值为非auto
的<colgroup>
,增大贯穿其中的列的最小宽度,使使得合计起来至少和<colgroup>
一样宽。以此获得每一列的最大、最小宽度。
将表格的每个标题(caption)按
display: block
格式化后,放入假想的单元格得到的 MCW,就是改标题的「最小外部宽度」,其最大者即为「最小标题宽度」(CAPMIN)。(未完待翻……)
呃!实在翻不下去连篇累牍的规范了,直接看重点吧:
A percentage value for a column width is relative to the table width. If the table has 'width: auto', a percentage represents a constraint on the column's width, which a UA should try to satisfy. (Obviously, this is not always possible: if the column's width is '110%', the constraint cannot be satisfied.)
.ew-v1 li {
display: table-cell;
width: 1%;
}
通过将元素设置为 display: table-cell
正是利用了自动布局算法,给 table-cell
元素设置的百分比宽度很小,但浏览器渲染的时需要确保 table-cell
元素填满表格宽度(和包含块的宽度一致),因此单元格被尽可能地扩展。
因为设置的百分比数值一样,table-cell
元素渲染出来近似等宽(实际上并不相等)。假如某个元素设置的百分比更大,那渲染出来也会占据更大的空间。而且,table-cell
元素包含内容的长度也会影响宽度的分配。
宽度设置为 1%
,主要是方便添加更多元素后仍能实现预期效果。
实际上,元素容器是否设置 display: table
无关紧要,因为 table-cell
元素外围会生成一个匿名表格容器,保证正确的渲染结果。
Fixed table layout
固定表格布局算法,表格和列宽度由 <table>
和 <col>
元素的设定的宽度或者是第一行的单元格宽度决定。后面的单元格不影响列宽度。
应用此算法后,一旦表格的第一行下载、解析完成,整个表格就可以被渲染,相对于自动布局算法,可以提高渲染速度。后面的单元格内容适应方式可以通过 overflow
属性设置。
In the fixed table layout algorithm, the width of each column is determined as follows:
- A column element with a value other than
'auto'
for the'width'
property sets the width for that column.- Otherwise, a cell in the first row with a value other than
'auto'
for the'width'
property determines the width for that column. If the cell spans more than one column, the width is divided over the columns.- Any remaining columns equally divide the remaining horizontal table space (minus borders or cell spacing).
上述第 3 点道出了下面的 CSS 实现等分效果的玄机。
.numbers-summary {
display: table; /* 1 */
table-layout: fixed; /* 2 */
width: 100%; /* 3 */
}
.numbers-summary li {
display: table-cell; /* 4 */
}
- 以
table
的形式渲染; - 使用固定表格布局算法:使未设置宽度的单元格平均分配水平空间;
- 保证表格填满容器(否则,表格宽度只渲染为内容宽度之和);
- 使子元素以表格单元格的形式渲染。
至此,已经揭开了两种实现方式背后的原理。
二者差异及兼容性
两种实现方式可能存在略微的性能差异,但几乎可以忽略,主要的差异在渲染结果上:
- 自动布局算法:没有完全等分,而且受单元格内容长度影响,即便长度一致,差异也很大;
- 固定布局算法:完全等分(由于浏览器计算原因会有 1px 差异),不受内容长度影响。
所以,选择哪种方式就显而易见了。
浏览器兼容:
- Can I Use 数据 :IE8+ 及其他浏览器都支持
display: table-*
属性; table-layout
: IE5 等古董浏览器都支持。
所以,放心使用吧。
写在最后
Web 开发中若遇到迷惑的问题,试着翻翻规范,说不定你就提壶光腚、茅厕蹲开了。
通过组合一些看似不起眼甚至枯燥乏味的属性,实现「神奇」的效果,正式 CSS 的精髓和乐趣所在。
参考链接
- Understanding CSS table-cell and percentage width
- Table Formatting
- Table width algorithms: the 'table-layout' property
- CSS 3 Tables Algorithms
- Fixed Table Layouts
咦!竟然有这「算法」这么高大上的东东。科班大神们就别欺负半路出家的前端、拿算法虐我们了……Amen.