零.术语概念
涉及术语:
伸缩容器(flex container)
伸缩项(flex item)
主轴(main axis)
交叉轴(cross axis)
主尺寸(main size)、主尺寸属性(main size property)
交叉尺寸(cross size)、交叉尺寸属性(cross size property)
伸缩行(flex line)
伸缩容器是display
的计算值为flex
或inline-flex
的元素,其流内孩子就是伸缩项(flex item)
A flex container is the box generated by an element with a computed display of flex or inline-flex. In-flow children of a flex container are called flex items and are laid out using the flex layout model.
(摘自2. Flex Layout Box Model and Terminology)
伸缩容器中的伸缩项按行排列/对齐,每一行都是伸缩行,类似于文本换行
主轴和交叉轴是两个方向,互相垂直,伸缩项沿着主轴排列。具体指横向(从左向右/从右向左)还是纵向(从上到下/从下到上)取决于flex-flow与writing mode。容器或伸缩项在主轴方向的尺寸就是主尺寸,在交叉轴方向的尺寸是交叉尺寸。例如,最常见的:
其中,主轴是从左向右的,交叉轴从上到下,容器的主尺寸是其width
值,容器的交叉尺寸是其height
值(主尺寸属性和交叉尺寸属性分别是width
和height
属性)
P.S.其它尺寸相关的通用术语,见2. Terminology
一.容器属性与伸缩项
flex相关的CSS属性分为两类:作用于容器的(容器属性),与作用于伸缩项的(伸缩项属性)
容器属性
display: flex | inline-flex
:分别用来定义块级与行内级伸缩容器盒,为元素创建伸缩格式化上下文(flex formatting context)flex-direction: row | row-reverse | column | column-reverse
:默认row
,定义伸缩容器的主轴方向,不带-reverse
的表示与由writing-mode
确定的行内轴(inline axis)方向相同,带的相反flex-wrap: nowrap | wrap | wrap-reverse
:默认nowrap
,定义内容是否允许换行,并定义交叉轴方向(新行从底部还是顶部开始),带-reverse
的与由writing-mode
确定的方向相反flex-flow: <flex-direction> || <flex-wrap>
:简写属性,可以出现1个或2个值,顺序无所谓justify-content: flex-start | flex-end | center | space-between | space-around
:默认flex-start
主轴起始端对齐,定义各行内容的主轴对齐方式,分别表示起始端、结束端、居中、各项之间均匀留空与各项左右均匀留空align-items: flex-start | flex-end | center | baseline | stretch
:默认stretch
拉伸占满交叉轴方向的空间,定义各行内容的交叉轴对齐方式,分别表示起始端、结束端、居中、基线对齐与拉伸铺满align-content: flex-start | flex-end | center | space-between | space-around | stretch
:默认stretch
各行均匀拉伸铺满交叉轴方向的空间,定义多行内容的整体相对于容器的对齐方式,值含义与justify-content
类似(多一个stretch
),只是针对行而言
与BFC和IFC相比,伸缩格式化上下文(FFC)有一些特殊性:
伸缩容器的
margin
不与内容margin
发生合并(collapse)伸缩项的
float
和clear
都无效伸缩项的
vertical-align
无效::first-line
和::first-letter
伪元素不适用于伸缩容器,并且伸缩容器自己不算作祖先元素的首行或首字母
两轴方向受writing-mode
影响,比如日文与英文在相同的flex
属性下效果不同,具体示例见Example 5
伸缩项属性
flex-grow
、flex-shrink
、flex
、flex-basis
:都会影响伸缩项拉伸、收缩,后面专门介绍align-self: auto | flex-start | flex-end | center | baseline | stretch
:默认auto
取容器的align-items
值,针对单伸缩项定义其交叉轴对齐方式,值含义与align-items
相同order: 整数
:默认0
,定义伸缩项在伸缩容器中的出现顺序(允许与源文档顺序不同),伸缩项按order
值从低到高排列,相等的就按文档序
P.S.特殊地,绝对定位元素的order
当0
处理,所以其它伸缩项的order
仍会影响绝对定位元素的位置(规范这么说,但实际上目前(2018/08/09
)主流浏览器似乎并没有这样做,当绝对定位元素的order
为极小值处理了)
P.S.另外,order
属性只影响视觉媒体(只是视觉上重新排序,而不是逻辑上的)。也就是说,在听觉媒体上,仍然是按文档序读出的,所以该属性可能会带来可访问性方面的问题
二.对齐方式
主轴方向的对齐方式,由容器的justify-content
控制:
交叉轴方向的对齐方式,由容器的align-items
与伸缩项的“align-self`共同决定(后者优先):
各行在交叉轴方向的对齐方式,由align-content
控制:
另外,多行场景下,每行内容独立布局,所以justify-content
和align-self
属性都相对当前行,而不是伸缩容器
Once content is broken into lines, each line is laid out independently; flexible lengths and the justify-content and align-self properties only consider the items on a single line at a time.
三.伸缩性(flexibility)属性
伸缩性(flexibility)是说元素能够按照既定规则改变自身宽/高适应容器的主轴尺寸,比如拉伸填满剩余空间,或者收缩自身尺寸以适应空间不足的情况
altering their width/height to fill the available space in the main dimension.
A flex container distributes free space to its items (proportional to their flex grow factor) to fill the container, or shrinks them (proportional to their flex shrink factor) to prevent overflow.
(摘自7. Flexibility)
伸缩性属性(components of flexibility),指的是影响伸缩项缩放的几个属性,分别是:
flex-basis
(尺寸基准)flex-grow
(拉伸因子)felx-shrink
(收缩因子)
P.S.如果要让元素不可伸缩,让拉伸因子和收缩因子都为0
即可(flex-grow: 0; flex-shrink: 0
,或者简写属性flex: none
等价于flex: 0 0 auto
)
flex-basis
用来设置伸缩项(flex item)所占空间基准,接受的值与width/height
相同,另外还支持auto
和content
:
长度值:数值加单位的形式
百分比:相对于伸缩容器的内主尺寸(inner main size)
inherit
:取父元素该属性的计算值auto
:伸缩项的尺寸取自主尺寸属性(main size,指的是width
或height
,取决于伸缩容器的主轴方向)content
:基于伸缩项的内容自动计算尺寸
content
相当于基准为auto
并且主尺寸也为auto
时伸缩项的尺寸,所以flex-basis: content
等价于:
flex-basis: auto;
/* 主轴是横向,主尺寸为width */
width: auto;
/* 或者,主轴是纵向,主尺寸为height */
height: auto;
P.S.content
值是后来新增的,所以兼容性不如双auto
好,可以考虑上面的替代方案
flex-basis
对缩放的影响见下例:
<div style="display: flex; width: 400px">
<span style="width: 10px; flex-basis: 0; flex-grow: 1; background-color: #ddd">10px</span>
<span style="width: 20px; flex-basis: 0; flex-grow: 1; background-color: #ccc">20px</span>
<span style="width: 10px; flex-basis: 0; flex-grow: 2; background-color: #eee">10px</span>
</div>
<div style="display: flex; width: 400px">
<span style="width: 10px; flex-basis: auto; flex-grow: 1; background-color: #ddd">10px</span>
<span style="width: 20px; flex-basis: auto; flex-grow: 1; background-color: #ccc">20px</span>
<span style="width: 10px; flex-basis: auto; flex-grow: 2; background-color: #eee">10px</span>
</div>
在一般环境中(英文writing-mode
),呈现效果如下:
| 100px | 100px | 200px |
| 100px | 110px | 190px |
第一种场景具体布局过程为:
内容初始宽度 = 0 + 0 + 0 = 0
剩余可分配宽度 = 400 - 0 = 400
按flex-grow分配剩余宽度,从左向右依次为
400 * 1 / (1 + 1 + 2) = 100
400 * 1 / (1 + 1 + 2) = 100
400 * 2 / (1 + 1 + 2) = 200
修正各伸缩项初始宽度,依次为
0 + 100 = 100
0 + 100 = 100
0 + 200 = 200
类似地,第二种为:
内容初始宽度 = 10 + 20 + 10 = 40
剩余可分配宽度 = 400 - 40 = 360
按flex-grow分配剩余宽度,从左向右依次为
360 * 1 / (1 + 1 + 2) = 90
360 * 1 / (1 + 1 + 2) = 90
360 * 2 / (1 + 1 + 2) = 180
修正各伸缩项初始宽度,依次为
10 + 90 = 100
20 + 90 = 110
10 + 180 = 190
P.S.注意,默认flex-basis
默认影响的是内容框尺寸,除非box-sizing
指定了其它值
P.S.根据指定值无法计算的特殊情况(比如指定了百分比值,而包含块的尺寸不确定),当做content
处理
flex-grow
拉伸因子,内容不足以占满伸缩行时依据flex-grow
值确定各项将额外获得空间的比例:
拉伸比例 = 当前项的flex-grow / 当前行所有项的flex-grow之和
例如3列等比布局:
<div style="display: flex; width: 300px">
<span style="flex: 1; background-color: #ddd">韭叶</span>
<span style="flex: 1; background-color: #ccc">大宽</span>
<span style="flex: 1; background-color: #eee">荞麦棱</span>
</div>
呈现效果是:
| 100 | 100 | 100 |
符合预期,这是因为flex: 1
等价于flex: 1 1 0
(见下文flex
简写属性部分),相当于同时设置了flex-grow: 1; flex-basis: 0
,其效果就是把300px
都当做额外空间,并按1:1:1
分配
flex-shrink
收缩因子,伸缩行装不下该行内容时依据flex-shrink
与基础尺寸(见下文布局算法部分)确定各项将收缩空间的比例:
收缩比例 = 当前项的flex-shrink值 * 基础尺寸 / 当前行所有项的(flex-shrink值 * 基础尺寸)之和
收缩的情况相对麻烦一些,涉及概念较多,具体示例见下文布局算法部分
flex
flex: none | [ <‘flex-grow’> <‘flex-shrink’>? || <‘flex-basis’> ]
简写属性,默认flex: 0 1 auto
(默认各项按内容尺寸比例收缩,不拉伸)。由于不带单位的0
对这三个属性而言都是合法的,所以flex
简写属性中的flex-basis
要么带单位,要么前面有两个伸缩因子值(数值),否则都会被当做伸缩因子:
flex: 0; /* 等价于 flex: 0 1 0%; */
flex: 0px; /* 等价于 flex: 1 1 0px; */
flex: 0 0; /* 等价于 flex: 0 0 0%; */
flex: 0 0px; /* 等价于 flex: 0 1 0px; */
flex: 1 1 0; /* 等价于 flex: 1 1 0px; */
注意,奇怪的是,flex-grow
和flex-shrink
的初始值似乎会变来变去的,还与默认值不相同,这是出于方便常见场景考虑:
Note: The initial values of flex-grow and flex-basis are different from their defaults when omitted in the flex shorthand. This is so that the flex shorthand can better accommodate the most common cases.
简言之,使用flex
简写属性的话,省略的子属性值会根据常见场景来赋予初始值,而不直接取默认值,例如:
flex: initial
等价于flex: 0 1 auto
flex: auto
等价于flex: 1 1 auto
flex: none
等价于flex: 0 0 auto
flex: 正数
等价于flex: 正数 1 0
P.S.常见场景具体见7.1.1. Basic Values of flex
四.布局算法
生成匿名伸缩项(针对伸缩容器中的文本孩子)
确定(伸缩)行的长度,分3步:
确定主轴、交叉轴的可用空间
确定每个伸缩项的基础尺寸(flex base size)和假定主尺寸(hypothetical main size)
确定伸缩容器的主尺寸(伸缩项的
auto
外边距先当成0
)
确定主尺寸
把伸缩项按行排列(1行或多行)
计算每一项的可伸缩长度
确定交叉尺寸
确定每个伸缩项的假定交叉尺寸(hypothetical cross size)
计算每一行的交叉尺寸
处理
align-content: stretch
(让这些伸缩行铺满交叉轴可用空间)处理
visibility: collapse
的伸缩项(这些项主尺寸为0
,但仍具有交叉尺寸,即能够影响所在伸缩行的交叉尺寸)确定每个伸缩项的交叉尺寸应用值(used cross size)
处理主轴对齐(逐行为主轴方向具有
auto margin
的伸缩项分配剩余可用空间,并根据justify-content
进行对齐)处理交叉轴对齐
处理交叉轴方向具有
auto margin
的伸缩项逐项按照
align-self
对齐(针对交叉轴方向不具auto margin
的伸缩项)确定伸缩容器的交叉尺寸应用值(used cross size)
所有伸缩行整体根据
align-content
对齐
P.S.详细布局规则需要考虑各种情况,繁琐复杂,具体见9. Flex Layout Algorithm
计算基础尺寸与假定主尺寸
先不考虑伸缩,进行第一次空间分配(即确定各项的假定主尺寸)
基础尺寸的具体计算步骤如下:
若设置了确定的
flex-basis
,就用这个值若伸缩项有固有宽高比(intrinsic aspect ratio),并且
flex-basis
值为content
,还有确定的交叉尺寸的话,根据交叉尺寸和宽高比计算若
flex-basis
值为content
,或取决于可用空间,并且伸缩容器有min-content
或max-content
约束,就用该伸缩项的最终主尺寸(resulting main size)否则,若
flex-basis
值为content
,或取决于可用空间,并且可用主尺寸为无限大,该伸缩项的行内轴还与主轴平行的话,按照writing-mode
中的正交流中盒的布局规则来处理,基础尺寸取其最大内容主尺寸(max-content main size),在多语言环境可能出现这种情况否则,用
flex-basis
的应用值代替其主尺寸,并把content
当做max-content
来计算主尺寸,如果还依赖交叉尺寸,而交叉尺寸为auto
或不确定的话,就当其交叉尺寸是fit-content
,取其最终主尺寸作为基础尺寸
计算基础尺寸时忽略min/max
尺寸限制,假定主尺寸就是加上这个限制后,得到的主尺寸值
计算可伸缩长度(Flexible Length)
伸缩布局,最关键的问题就是如何伸缩(即空间的二次分配,计算各项将因伸缩属性额外获得或失去的空间),步骤如下:
确定伸缩因子的应用值
把该行所有伸缩项的假定主尺寸加起来,小于伸缩容器内主尺寸的话,把
flex-grow
作为伸缩因子,否则就用flex-shrink
确定不可伸缩项(inflexible item)的尺寸
把假定主尺寸作为其最终目标主尺寸(target main size),不再变了
不可伸缩项指的是:伸缩因子值为
0
的、伸缩因子是flex-grow
并且基础尺寸大于假定主尺寸的、伸缩因子是flex-shrink
并且基础尺寸小于假定主尺寸的计算初始剩余空间
伸缩容器的内主尺寸,减去该行所有项的外尺寸之和,就是初始剩余空间
若是不可伸缩项,取其外目标主尺寸,否则取其外基础尺寸
循环处理
检查该行每一项,如果所有项都确定了最终目标主尺寸,结束
计算剩余可用空间作为上面的初始剩余空间
若未确定最终目标主尺寸的所有项伸缩因子之和小于
1
,用该值乘以初始剩余空间,如果得到的值的小于剩余可用空间值,就把这个值作为剩余可用空间根据伸缩因子按比例分配剩余空间
剩余可用空间为
0
,结束伸缩因子是
flex-grow
的话,计算该项的flex-grow
值除以该行所有未确定最终目标主尺寸的伸缩项的flex-grow
值之和,得到一个比例,目标主尺寸就是基础尺寸加上剩余可用空间中该比例对应的那部分伸缩因子是
flex-shrink
的话,对于该行每一个未确定最终目标主尺寸的伸缩项,用其内基础尺寸乘以其伸缩因子,称为比例比例收缩因子(scaled flex shrink factor)。用该项的比例收缩因子除以该行所有未确定最终目标主尺寸的伸缩项的比例收缩因子,得到一个比例,目标主尺寸就是其基础尺寸减去剩余可用空间中该比例对应部分的绝对值
处理可伸缩项的
min/max
限制(如果有的话),把目标主尺寸裁剪到该范围处理伸缩过的项
经上一步裁剪后,如果总尺寸没变(各项需调整差值之和为
0
),结束总尺寸变大了,(上一步中)所有违背
min
限制的项确定最终目标主尺寸总尺寸变小了,所有违背
max
限制的项确定最终目标主尺寸
回到循环开始处
把每一项的主尺寸应用值设置为目标主尺寸
其中,最重要的部分是如何确定拉伸比例与收缩比例(比例相对剩余可用空间),用公式描述如下:
// growFactor 拉伸因子,flex-grow值
// growFactors 该行所有项的拉伸因子
// shrinkFactor 收缩因子,flex-shrink值
// scaledShrinkFactor 比例收缩因子
// scaledShrinkFactors 该行所有项的比例收缩因子
// baseSize 基础尺寸
// freeSpace 剩余可用空间
// 拉伸
growRatio = growFactor / sum(growFactors)
// 收缩
scaledShrinkFactor = baseSize * shrinkFactor
shrinkRatio = scaledShrinkFactor / sum(scaledShrinkFactors)
确定比例之后,修正基础尺寸:
// 拉伸
targetSize = baseSize + growRatio * freeSpace
// 收缩
targetSize = baseSize - abs(shrinkRatio * freeSpace)
例如,最简单的场景:
<div style="display: flex; width: 400px">
<span style="flex-basis: 200px; flex-shrink: 1; background-color: #ddd">200px</span>
<span style="flex-basis: 400px; flex-shrink: 1; background-color: #ccc">400px</span>
<span style="flex-basis: 200px; flex-shrink: 2; background-color: #eee">200px</span>
</div>
呈现效果如下:
| 120px | 240px |40px|
具体布局过程是这样:
freeSpace = 400 - (200 + 400 + 200) = -400
scaledShrinkFactors = [1 * 200, 1 * 400, 2 * 200]
sum(scaledShrinkFactors) = 1000
shrinkRatios = [0.2, 0.4, 0.4]
targetSizes = [
200 - abs(0.2 * -400) = 120,
400 - abs(0.4 * -400) = 240,
200 - abs(0.4 * -400) = 40
]
当然,这只是最简单的示例场景(伸缩容器可用空间、flex-basis
都是固定值),很容器计算基础尺寸、剩余空间及收缩比例,实际应用场景要复杂得多
五.应用场景
按比例布局(几行几列)
对齐控制(横向、纵向居中等)
自适应容器尺寸(铺满或溢出收缩)
这些之前难以实现的场景,在flexbox布局中都很容易搞定。实际上,真正难以驾驭的恰恰是那些之前很容易实现的场景
P.S.为什么非得用felxbox布局?结合使用,各取所长不好吗?因为有些场景没得选,比如RN等基于yoga引擎的CSS环境(只支持flexbox布局)
比如要求icon贴着单行文本的场景,不用flexbox布局的话,可以这样实现:
<div style="width: 100px;">
<span style="display: inline-block; max-width: 70px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">不长</span><span style="display: inline-block; width: 30px; background-color: #ccc">icon</span>
<span style="display: inline-block; max-width: 70px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">很长很长很长很长很长很长</span><span style="display: inline-block; width: 30px; background-color: #ccc">icon</span>
</div>
非要用的话,这样做:
<div style="width: 100px; display: flex; flex-wrap: wrap">
<span style="flex-shrink: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">不长</span><span style="width: 30px; background-color: #ccc">icon</span>
<div style="max-width: 100%; display: flex">
<span style="flex-shrink: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">很长很长很长很长很长很长</span><span style="flex: none; width: 30px; background-color: #ccc">icon</span>
</div>
</div>
关键点在于文本flex-shrink
缩回来,这样在文本溢出时能够收缩回来,给icon留出足够的空间,未溢出时,收缩不影响文本宽度,右侧icon就能够紧贴着
另外,第二行容器的max-width: 100%
很重要,作为基础尺寸的约束条件。icon的flex: none
也很重要,避免假icon的宽度受到挤压(因为flex-shrink
默认值是1
,空间不足时也会跟着收缩)
参考资料
CSS Flexible Box Layout Module Level 1:还处于候选流程,离定稿REC还早,内容尚不稳定