CSS杂谈——flex布局里面的auto到底多长

发布时间 2023-04-11 23:30:14作者: 轩_雨

本篇博客将以 “ flex 布局里设置 auto 的区块怎么让文案超出省略的问题” 作为切入点,来分析一下 flex 布局里面各子项的具体长度到底怎么计算。

从需求出发谈 flex 布局

我们有一个 H5 项目,类似于微信的通讯录界面,前面是头像,后面是昵称。

所以我们一开始的代码是这么写的:

<div style="display: flex;width: 400px;line-height: 100px;border: 12px solid black">
    <div style="flex: 0 0 100px;background-color: #00ffff7d;height: 100px;text-align: center">pic</div>
    <div style="flex: 1 1 auto;background-color: #deb8877d;height: 100px;">
        00000000000000000000
    </div>
</div>
<!-- 实际开发我们是用antd来实现的,这里写html只算是用于分析的伪代码。-->
<!-- 然后为了显示效果border设置得十分明显,宽度也固定为400(实际开发还是要适配各个机型的宽度的)。-->
pic
00000000000000000000

用人话来翻译一下这段代码,就是:

第一个 flex 布局子项占 100,且有剩余不占,有超出不减;

第二个 flex 布局子项自由适配,有剩余就占,有超出就减。

所以就达到了头像恒占 100,剩下部分占据剩余宽度的效果。

这个时候,我们需要关心的还有一个问题,就是昵称的长度问题,如果昵称长度太长了我们需要省略一下。所以我们对代码做了如下修改:

<div style="display: flex;width: 400px;line-height: 100px;border: 12px solid black">
    <div style="flex: 0 0 100px;background-color: #00ffff7d;height: 100px;text-align: center">pic</div>
    <div style="flex: 1 1 auto;background-color: #deb8877d;height: 100px;">
    	<div style="text-overflow: ellipsis;overflow: hidden;white-space: nowrap">
            00000000000000000000000000000000000000000000000000000000
        </div>
    </div>
</div>
pic
00000000000000000000000000000000000000000000000000000000

但 flex 布局的 flex-basis 设置为 auto 后,省略却不会如期生效。

这个时候可以用三种方法去解决这个问题:

  1. 设置第二个 flex 布局子项 width 为小于剩余宽度的值,由于剩余宽度不好确定,设置为0最保险

    <div style="display: flex;width: 400px;line-height: 100px;border: 12px solid black">
        <div style="flex: 0 0 100px;background-color: #00ffff7d;height: 100px;text-align: center">pic</div>
        <div style="flex: 1 1 auto;background-color: #deb8877d;height: 100px;width: 0;">
        	<div style="text-overflow: ellipsis;overflow: hidden;white-space: nowrap">
             00000000000000000000000000000000000000000000000000000000
            </div>
        </div>
    </div>
    
    pic
    00000000000000000000000000000000000000000000000000000000
  2. 设置第二个 flex 布局子项 min-width 为小于剩余宽度的值,由于剩余宽度不好确定,设置为0最保险

    <div style="display: flex;width: 400px;line-height: 100px;border: 12px solid black">
        <div style="flex: 0 0 100px;background-color: #00ffff7d;height: 100px;text-align: center">pic</div>
        <div style="flex: 1 1 auto;background-color: #deb8877d;height: 100px;min-width: 0;">
        	<div style="text-overflow: ellipsis;overflow: hidden;white-space: nowrap">
             00000000000000000000000000000000000000000000000000000000
            </div>
        </div>
    </div>
    
    pic
    00000000000000000000000000000000000000000000000000000000
  3. 设置第二个 flex 布局子项 overflow为 hidden(实际上设置 scroll、auto 都是可以的,但滚动,多少有些奇怪吧)

    <div style="display: flex;width: 400px;line-height: 100px;border: 12px solid black">
        <div style="flex: 0 0 100px;background-color: #00ffff7d;height: 100px;text-align: center">pic</div>
        <div style="flex: 1 1 auto;background-color: #deb8877d;height: 100px;overflow: hidden;">
        	<div style="text-overflow: ellipsis;overflow: hidden;white-space: nowrap">
             00000000000000000000000000000000000000000000000000000000
            </div>
        </div>
    </div>
    
    pic
    00000000000000000000000000000000000000000000000000000000

在解释这些设置为什么能解决这个问题之前,先来重新认识一下 flex 布局,毕竟长时间使用各个 UI 组件库的 Grid 后,自己也部分丧失了对弹性布局的理解。

Flex 布局的扩缩策略

我们这里不去讨论 flex 布局中 wrap 的部分,也只以水平方向的 flex 布局为例。尽量先简单地理解,如果大家对 flex 布局想要了解得更深,可以阅读 w3c 关于 CSS Flexible Box Layout Module Level 1 (w3.org) 的最新内容。

我们先了解一下 flex 这个决定布局扩缩策略的 CSS 属性。

flex 属性是以下 CSS 属性的简写:

  • flex-grow
  • flex-shrink
  • flex-basis
.flex-container {
	flex: 0 0 100px
}

就是代表

.flex-container {
	flex-grow: 0;
	flex-shrink: 0;
	flex-basis: 100px;
}

我们这里仅讨论三值语法,关于flex单值语法、双值语法的与flex-grow、flex-shrink、flex-basis的对应关系不在这里展开讨论,详细可以看 flex - CSS:层叠样式表 | MDN (mozilla.org) 这篇MDN文档。

接下来,先介绍一下flex-growflex-shrink两个扩缩相关的。

flex-grow

这个属性规定了 flex 布局子项在 flex 容器中分配剩余空间的相对比例,这个属性的默认值为0。

剩余空间是 flex 容器的大小减去所有 flex 项的大小加起来的大小。如果所有的 flex 布局子项的 flex-grow 系数加起来小于等于1,则剩余空间直接用 flex-grow 系数进行分配(有空余也不会接着分配);否则把 flex-grow 系数当成权重进行分配。

flex-grow 系数加起来小于等于1

<div style="display: flex;width: 400px;border: 12px solid black">
    <div style="flex-grow: 0.2;width: 100px;background-color: aqua;height: 100px;"></div>
    <div style="flex-grow: 0.3;width: 100px;background-color: burlywood;height: 100px;"></div>
</div>
|- 140px -|
|- 160px -|

\[\text{第一个子项(青色)的宽度:}W1 = \text{子项的初始大小:}100 + \text{剩余宽度:}200 * \text{flex-grow:}0.2 = 140 \\ \text{第二个子项(棕色)的宽度:}W2 = \text{子项的初始大小:}100 + \text{剩余宽度:}200 * \text{flex-grow:}0.3 = 160 \]

flex-grow 系数加起来大于1

<div style="display: flex;width: 400px;border: 12px solid black">
    <div style="flex-grow: 2;width: 100px;background-color: aqua;height: 100px;"></div>
    <div style="flex-grow: 3;width: 100px;background-color: burlywood;height: 100px;"></div>
</div>
|- 180px -|
|- 220px -|

\[\text{第一个子项(青色)的宽度:}W1 = \text{子项的初始大小:}100 + \text{剩余宽度:}200 * \text{比例:}\frac{2}{2 + 3} = 140 \\ \text{第二个子项(棕色)的宽度:}W2 = \text{子项的初始大小:}100 + \text{剩余宽度:}200 * \text{比例:}\frac{3}{2 + 3}> = 160 \]

flex-shrink

这个属性规定了 flex 布局子项在 flex 容器中的收缩规则,这个属性的默认值为1。

flex 布局子项在原始宽度之和大于容器的时候才会发生收缩。与 flex-grow 一样,如果所有的 flex 布局子项的 flex-shrink 系数加起来小于等于1,则超出部分直接用 flex-shrink 系数进行缩减(有超出也不会接着缩减);否则把 flex-shrink 系数当成权重进行超出部分的缩减。

flex-shrink 系数加起来小于等于1

<div style="display: flex;width: 400px;border: 12px solid black">
    <div style="flex-shrink: 0.2;width: 300px;background-color: aqua;height: 100px;"></div>
    <div style="flex-shrink: 0.3;width: 300px;background-color: burlywood;height: 100px;"></div>
</div>

\[\text{第一个子项(青色)的宽度:}W1 = \text{子项的初始大小:}300 - \text{超出宽度:}200 * \text{flex-shrink:}0.2 = 260 \\\text{第二个子项(棕色)的宽度:}W2 = \text{子项的初始大小:}300 - \text{超出宽度:}200 * \text{flex-shrink:}0.3 = 240 \]

flex-shrink 系数加起来大于1

<div style="display: flex;width: 400px;border: 12px solid black">
    <div style="flex-shrink: 2;width: 300px;background-color: aqua;height: 100px;"></div>
    <div style="flex-shrink: 3;width: 300px;background-color: burlywood;height: 100px;"></div>
</div>

\[\text{第一个子项(青色)的宽度:}W1 = \text{子项的初始大小:}300 - \text{超出宽度:}200 * \text{比例:}\frac{2}{2 + 3} = 220 \\\text{第二个子项(棕色)的宽度:}W2 = \text{子项的初始大小:}300 - \text{超出宽度:}200 * \text{比例:}\frac{3}{2 + 3} = 180 \]

flex-basis: auto 的计算规则

flex-basis 之所以单独介绍,是因为他是我们今天讨论的问题围绕的关键CSS属性

flex-basis 大部分时候是可以等同于 width的(flex-basis 的优先级比width高),但设置为 auto 的时候是比较特殊的,而且有较为复杂的发展历史。

备注: 简史

  • 最初,"flex-basis:auto" 的含义是 "参照我的widthheight属性".
  • 在此之后,"flex-basis:auto" 的含义变成了自动尺寸,而 "main-size" 变成了 "参照我的widthheight属性"。实际执行于 bug 1032922.
  • 然后呢,这个更改又在 bug 1093316 中被撤销了,所以 "auto" 变回了原来的含义; 而一个新的关键字 'content' 变成了自动尺寸。 (bug 1105111 包括了增加这个关键字).

不过这个历史了解一下就好,它跟我们今天讨论的问题并没有关系。当我们没有给 flex 布局子项设置width时,flex-basis: auto由内部的content决定宽度,和flex-basis: content是一样的。

在上面,我们通过三种方法,让 flex-basis 为 auto 的 flex 布局子项文案省略可以生效。

设置 flex 布局子项 width 为 0,让宽度的初始值为 0,再基于 flex-grow 扩张,而不是以子项的 content 宽度为准,从而保障宽度不溢出,这听起来很好理解。

但是设置 flex 布局子项 overflow 为 hidden、设置 min-width 为 0,从而让宽度不以子项的 content 宽度为准,这听起来就不是一个很好理解的做法。

设置 flex 布局子项 overflow 为 hidden

因为设置 flex 布局子项 overflow 为 hidden 和设置 min-width 为 0 是一个道理,我们先来看看设置 overflow 为 hidden 是怎么等同设置 min-width 为 0 的。

为此,我们需要先阅读一下 CR-css-flexbox-1-20181119 的 4.5节 Automatic Minimum Size of Flex Items

To provide a more reasonable default minimum size for flex items, the used value of a main axis automatic minimum size on a flex item that is not a scroll container is a content-based minimum size; for scroll containers the automatic minimum size is zero, as usual.

从这段话可以得出设置 overflow 为 scroll、auto 可以让 min-width 为 0,所以设置 overflow 为 scroll 和设置 min-width 为 0 在宽度计算的影响方面是等效的。那设置 overflow 为 hidden 呢?

我们来看一下 CR-css-flexbox-1-20181119 从 2016 年之后的处于候选推荐阶段的一些东西 Changes since the 1 March 2016 CR

value to be easier to understand. ([Issue 9](CSS Flexible Box Layout Level 1 Disposition of Comments for 2016-03-01 CR (csswg.org)))

On a flex item whose overflow is visible in the main axis, when specified on the flex item’s main-axis min-size property, the following table gives the minimum size … specifies an automatic minimum size.

In general, the automatic minimum size … defined below:

这里的意思就是为了方便理解,不再是说 overflow 设置为滚动才会使 min-width 为 0,而是只有 overflow 设置为 visible 的时候,min-width 为 auto,其他时候都是 0。一般来讲,有相当部分的文档会在候选推荐阶段就得到浏览器厂商的认可,并用于实践。所以,现在大多数的浏览器也是 overflow 不为 visible 时 min-width 为 0。

设置 flex 布局子项 min-width 为 0

接下来就是讨论为什么设置 flex 布局子项 min-width 为 0 可以让文案省略生效了。

我们已经知道 overflow 为 visible 时,min-width 为 auto,所以一般情况下,min-width 都为 auto。而由于弹性布局特有的扩缩容忍度,flex 布局子项的大小是可以延伸到弹性盒子外的。CR-css-flexbox-1-2018111 也写到了,如果 min-width 为 auto,flex 布局子项就会基于他的 content 去计算自己的最小宽度。

For the purpose of calculating an intrinsic size of the box (e.g. the box’s min-content size), a content-based minimum size causes the box’s size in that axis to become indefinite (even if e.g. its width property specifies a definite size). Note this means that percentages calculated against this size will behave as auto.

这个特性导致最小宽度如果超出原本他可以占据的空间,而 flex-shrink 的设置又受最小宽度的限制不能回缩,flex 布局子项就会溢出。

而省略文本由于设置了white-space: nowrap,导致空格或者其他分割、全角符号不能换行,此时文案越长,flex 布局子项的content越宽,flex 布局子项随着溢出,没有了外边的限制,内部也就失去了省略的必要。

In particular, if flex sizing is being used for a major content area of a document, it is better to set an explicit font-relative minimum width such as min-width: 12em. A content-based minimum width could result in a large table or large image stretching the size of the entire content area into an overflow zone, and thereby making lines of text gratuitously long and hard to read.

CR-css-flexbox-1-2018111 文档也说明了这样的特性会导致溢出的出现,需要开发者去指定一个最小宽度,所以指定最小宽度是一个官方的,合理的解决方案。

简单理解

如果长篇大段不好理解,其实有一个比较奇特的类比可以说明问题。一般来说,flex 布局设置 flex-basis 为 auto 的子项默认情况如下代码:

<div style="width: 400px;border: 12px solid black">
    <div style="min-width: fit-content;">
        <div style="text-overflow: ellipsis;overflow: hidden;white-space: nowrap;line-height: 100px">10000000000000000000000000000000000000000000000000000000000000000000</div>
    </div>
</div>
10000000000000000000000000000000000000000000000000000000000000000000

开发者需要用 min-width:0 覆盖 min-width: fit-content

CSS 的设计还是有其深奥的地方,平时过于依赖UI组件库果然是不行呢~