前言
在经历过table布局、div布局之后,flex逐渐完善且成为标准。这时候前端排版迎来了一次解放,很多难以书写的布局写法被大大简化了。时至今日,flex早已成为一个基础,无论是功能还是技能方面。它涵盖了日常开发中绝大部分场景。
本文不再讨论flex语法、功能和缩写,直接讲解flex如何实现即算法原理部分。对于应用方面还不了解的请先自行查阅。
准备
和其它普通文档流(如block)不同的是,flex的子节点的顺序比较特殊,其按照css声明的order
顺序排列,如果没有或相等则降级为节点序。因此第一步是把容器的文档流子节点先排序,结果记为orderChildren
。
我们叫flex为弹性布局的根本原因是每个子节点都是弹性的,它可以伸缩(grow/shrink),这样势必有一个基准值(basis)作为参考,如此才能在一个基础上决定伸缩情况。那么这个基准值怎么决定呢? www.w3.org/TR/css-flex…
根据规范,我们可得知(进行了一定程度简化,如不考虑min/max限制、竖排版文字、英文单词排版等):
- 如果节点定义了
flex-basis
,则使用它; - 如果节点是具有固定宽高比的特殊节点(如img),且
flex-basis
最终是content
,且交叉轴已知,那么根据宽高比计算后的主轴尺寸就是; auto
如果定义了主轴尺寸则使用它,如果没有则降级为content
;content
为内容自适应后的尺寸。
上面自适应是最难的,因为内容是不固定的,且内容本身的布局方式也是变化的。因此自适应需要有个计算内容尺寸有多大的过程(其实即便不是自适应也需要计算下面说的max/min)。
在了解这个过程之前,需要先知道2个基本概念:
- 最大尺寸(记max):指节点在当前环境下理想状态的尺寸;
- 最小尺寸(记min):指节点在当前环境下被压缩到最小状态的尺寸;
文字描述有点抽象,看图示例:
左边即第1点max,它假设可用空间是无限的,因此文本(或inline节点等)可以在一行内全部排下,即便超过flex父节点(黑边)也无所谓;
右边即第2点min,它假设空间完全不够用,但为了保证内容渲染至少要有单个字符(或英文单词等)的宽度,也不考虑是否超过flex父节点。
回到计算自适应的过程,我将它分为2个大部分:
- flex直接子节点的尺寸计算;
- 其后续递归子节点的尺寸计算。
先看第1大部分。flex的子节点是个匿名块容器,可认为强制是块级(block/flex,忽略inline-flex)。
当子节点是block时,我们需要结合flex-direction
(row/column)和递归子节点的类型(dom/text)来计算。它比较简单,在row情况下,先对block的子节点(此时关系层级为flex的孙子节点,下面都将简称孙子节点)进行上述第2大部分的计算,获取到max2/min2,然后看孙子节点的display
,block则独占一行,inline和文字则尝试理想状态下同行,如此便能获取到max/min的最大值(所有max相比最大,所有min相比最大)。basis最终就是max的值。
在column的情况下差不多,所有block类型的孙子节点独占一行从上到下排列时max/min是累加的,而inline和文字则是取同行里高度最大的那个。
当子节点是flex时,情况和上面block也类似,不过更加简化,因为flex子节点强制块级,因此孙子节点中没有inline更加容易计算。
再看第2大部分。它和第1大部分的区别在于节点可以有inline类型,其余部分一样,整体计算方式类雷同。
至此,我们获取了每个flex子节点的max/min值。
分行
接下来根据每个子节点的情况求假设主尺寸,这步比较简单,根据basis/max/min这3者之间的关系即可,用公式表达为:
hypothetical = clamp(min_main_size, flex_base_size, max_main_size)
伪代码形式为:
hypothetical_main_size:
if flex_base_size > max_main_size:
return max_main_size
if flex_base_size < min_main_size:
return min_main_size
return flex_base_size
根据flex-wrap
的声明,我们要判断容器内布局是单行(nowrap)还是多行(wrap),反向多行(wrap-reverse)和多行相同,只是顺序颠倒。那么怎么判断呢?用上面求得的假设主尺寸将orderChildren
依次排布,如果一行放不下则另起一行。当然如果声明是nowrap不需要换行(即只有一行),但无论哪种都需要统计每行的假设主尺寸之和。
布局
现在得到每一行的信息了,利用官方文档给定的如下算法:
- 确定使用的弹性因子。对所有项的外部假设主尺寸求和,如果和小于flex容器的主尺寸,则算法使用增长因子,否则使用收缩因子。即看用flex-grow还是flex-shrink。
- 非弹性尺寸项。冻结,设置它的目标主尺寸为其假设主尺寸。满足任意下列条件即为非弹性尺寸项:
- a. 弹性因子为0
- b. 如果使用增长因子,flex-basis计算值大于其假设主尺寸
- c. 如果使用收缩因子,flex-basis计算值小于其假设主尺寸
- 计算初始可用空间。对行上所有项的外部尺寸求和,再被减去flex容器的主尺寸。对于冻结项目来说,外部尺寸是指目标主尺寸;其它非冻结的则为其外部flex-basis计算值。
- 循环:
- a.检查每一项。如果所有的弹性项都被冻结了,则可用空间视为分配完毕,跳出循环。
- b.计算剩余可用空间并将它作为上条中提到的初始可用空间。如果未冻结的项的弹性因子之和小于1,则将初始可用空间乘以此和。如果这个值小于剩余可用空间,则设置它为新的剩余可用空间。
- c.根据弹性因子分配可用空间。
如果剩余可用空间是0 跳过。 如果使用增长因子 计算该项的增长因子占所有未冻结项的增长因子之和的比例。设置该项的目标主尺寸为flex-basis计算值加上比例乘以剩余可用空间。 如果使用收缩因子 对未冻结的每项,将其收缩因子和flex-basis计算值相乘,记为缩放收缩因子。求得所有缩放收缩因子的和,然后得出每项缩放收缩因子占和的比例。将项的目标主尺寸设置为flex-basis计算值减去比例乘以剩余可用空间的积。注意,这可能会导致主尺寸为负值,下一步将修正。 否则 跳过。
- d.修正最小/最大值违规问题。将每个非冻结项的目标主尺寸按其使用的最小/最大值固定(clamp函数的意思),限制其content-box为0。如果目标主尺寸小于最小值,则为最大违规。反之大于最大值,则为最小违规。
- e.冻结过渡弹性伸缩的项。总的违规数值为上一步中每项调整的综合 ∑(clamped size - unclamped size),即违规差值。如果这个数值是:
0 冻结所有项。 正数 冻结所有最小违规项。 负数 冻结所有最大违规项。 5. 设置每项的主尺寸为其目标主尺寸。
另外值得注意的是,规范中没有详细提及orderChildren
的mpb(margin/padding/border),以及递归孙子节点的mpb。直接item的无论单位如何都是考虑在内的;而孙子节点只考虑进min/max尺寸,且必须是固定值,其它如百分比则忽略。
反向
当反向多行时,需要多处理一步,将当前正向多行的内容进行倒序排列,注意单位是行,下面将以row举例。
记正向每行高度列表是maxCrossList
,行首为相对起点即y=0。统计出递增的高度列表crossSumList
,即前面所有行高度之和。
然后从末尾开始循环,设一个count
变量,每次循环结束增加maxCrossList
对应索引的值。记source
为crossSumList
对应索引的值,记diff
为count
减去source
的值,如果不为0,则进行偏移。
示例
<div style={{display:'flex',width:100}}>
<span style={{flex:'1 1 50',background:'#F00',padding:'0 5'}}>2</span>
<span style={{flex:'1 1 40',background:'#00F'}}>3</span>
</div>
<div style={{display:'flex',width:100}}>
<span style={{flex:'1 1 auto',background:'#F00'}}><strong style={{display:'block',padding:'0 5'}}>2</strong></span>
<span style={{flex:'1 1 auto',background:'#00F'}}><strong style={{display:'block'}}>3</strong></span>
</div>
<div style={{display:'flex',width:100}}>
<span style={{flex:'1 1 auto',background:'#F00'}}><strong style={{display:'block',padding:'0 5%'}}>2</strong></span>
<span style={{flex:'1 1 auto',background:'#00F'}}><strong style={{display:'block'}}>3</strong></span>
</div>
我们用这3个简化的例子来看过程,最终效果如下:
3个flex节点按顺序称之为A、B、C,它们很相似,区别在于mpb以及子节点。 A节点的孩子们basis为50和40,但由于首个声明了padding,所以最终是60和40,max同,min是19和9左右(字符2和字符3的尺寸,首个要算padding)。假设主尺寸和basis保持一致。因为60+40正好=100,所以最终他们就是60和40的宽度。
B节点的孩子们basis为auto,且没有width声明,所以降级为content自适应。首个节点的递归子节点多了padding,所以最终max、min和basis是19和9左右。假设主尺寸和basis保持一致,空余100-19-9=72,均分给2个节点每个36,最终他们是55和45的宽度。
C节点的孩子们basis为auto,且没有width声明,所以降级为content自适应。首个节点的递归子节点多了padding但百分比无效,所以最终max、min和basis都是9左右。假设主尺寸和basis保持一致,空余100-9-9=82,均分给2个节点每个41,最终他们都是50的宽度。
最后附上源码:github.com/karasjs/kar…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!