Flex 布局
2009年,W3C 提出了一种新的方案----Flex 布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。
那么浏览器又是如何实现这样的一套神奇的布局方式呢?下面的内容就带你详细了解一套简单易懂的布局代码,看看每个属性是如何作用于对应的 flex 元素,并如何计算出每个元素的位置并排版,最终实现一个完整的 flex 布局
主要流程
Flex 布局的主要流程可以分为以下几个方面
- 根据 flex 属性或默认值,初始化变量,浏览器通过这一步确认修改 flex 的默认行为
- 确定主轴和交叉轴及其方向、偏移起点终点等变量,比如确定主轴的变量是 width 还是 height
- 将元素收集进 FLexLine 弹性行,这一步会计算初始的 mainSpace 和 crossSpace,为后面计算主轴和交叉轴,每个元素的位置,空隙的大小等,提供初始量
- 根据主轴的几个变量, 确定起点偏移量和终点偏移量,确定方向,确定起点,将 flex 弹性元素放入主轴方向计算,形成 FlexLine 弹性行,同时会把 mainSpace 用到行内,让空隙按照符合设置的方式填充进剩余空间。也可以根据 flex-grow 和 flex-shrink 分配剩余的空隙
- 根据交叉轴的几个变量,确定起点偏移量和终点偏移量,确定方向,确定起点,将 FlexLine 弹性行放入交叉轴方向计算,形成 FlexBox 弹性盒子
function flexLayout(element) {
// 获取 style
const style = getStyle(element)
// 初始化 flex 属性
const initStyle = initSetting(style)
//
const flexProp: FlexProp = getFlexProperty(initStyle)
const flexLine: FlexLine = {
orderItems: [],
mainSpace: 0,
crossSpace: 0,
}
const flexLines: FlexLines = [flexLine]
const orderItems = getOrderItems(element);
const autoMainSize = computeAutoMainSize(initStyle, orderItems, element)
const lastFlexLine: FlexLine = collectItems(
initStyle,
orderItems,
flexLine,
flexLines,
flexProp,
autoMainSize
)
computeMainSpace(
initStyle,
orderItems,
lastFlexLine,
flexLines,
flexProp
)
computeCrossSpace(initStyle, flexLines, flexProp)
}
流程分解
1. 设置默认值
影响 flex 布局里的主要属性的默认值分配,按照如下表格初始化,父元素未定义的就用默认值
flex-direction | row |
---|---|
flex-wrap | no-wrap |
justify-content | stretch |
align-content | flex-start |
align-items | stretch |
// getStyle 获取元素 style
const style = getStyle(element)
// style 是节点的样式,style 中将 px 和 数字都转为数字
// 子元素 list
const items = element.node.children
const orderOfItems = Array.from(items)
orderOfItems.sort((item1, item2) => {
const { order1 } = getStyle(item1);
const { order2 } = getStyle(item2);
return order1 - order2
})
if (!style.flexDirection || style.flexDirection === 'auto') {
style.flexDirection = 'row'
}
if (!style.alignItems || style.alignItems === 'auto') {
style.alignItems = 'stretch'
}
if (!style.justifyContent || style.justifyContent === 'auto') {
style.justifyContent = 'flex-start'
}
if (!style.flexWrap || style.flexWrap === 'auto') {
style.flexWrap = 'no-wrap'
}
if (!style.alignContent || style.alignContent === 'auto') {
style.alignContent = 'stretch'
}
2. 确定变量并初始化
- 确定变量
-
- 根据 flex-direction 确定主轴和交叉轴:
-
- row:从左到右,width 为主轴,height 为交叉轴
- row-reverse:从右到左,width 为主轴,height 为交叉轴
-
- column:从上到下,height 为主轴,width 为交叉轴
- column-reverse:从下到上,height 为主轴,width 为交叉轴
-
- 确定以下几个变量:
-
-
- mainSize 主轴尺寸:主轴的空间大小
- crossSize 交叉轴尺寸:交叉轴的空间大小
-
-
-
- mainBase 主轴起始线:主轴所有元素开始排版的起点
- crossBase 交叉轴起始线:交叉轴所有 flexLine 开始排版的起点
-
-
-
- mainStart 主轴偏移起点:根据 step 和 mainSize 计算,每一个元素在进 flexLine 前的起点
- mainEnd 主轴偏移终点:根据 step 和 mainSize 计算,每一个元素在进 flexLine 后的终点
-
-
-
- mainSign 主轴排布方向,决定了元素往什么方向排布在主轴上
- crossStart 交叉轴偏移起点:根据 step 和 crossSize计算,每一个 flexLine 的起点
-
-
-
- crossEnd 交叉轴偏移终点:根据 step 和 crossSize计算,每一个 flexLine 前的终点点
- crossSign 交叉轴排布方向,决定了元素往什么方向排布在交叉轴上
-
下图的是默认情况的主轴和交叉轴及其变量
let mainSize, mainStart, mainEnd, mainSign, mainBase,
crossSize, crossStart, crossEnd, crossSign = 1, crossBase
- 初始化数值
- mainSize 和 crossSize 指的是弹性盒子的实际大小,也就是带上一系列偏移量、margin、padding 等后的实际大小
- 这里的 crossSign 和 mainSign 是可以根据 x 轴和 y 轴的方向改变的
- wrap-reverse 实际改变的是交叉轴,因此需要交叉轴本身改换 crossSign 和 crossStart、crossEnd,只是 crossBase 不改变,因为 crossBase 依据主轴的起点
// 为什么要用 left right 表示 Start 和 End? 是因为 left right 表示了元素一定的偏移量,实际上用的不是CSS 里的 left right 属性
if (style.flexDirection === 'row') {
mainSize = 'width'
mainStart = 'left'
mainEnd = 'right'
mainSign = +1
mainBase = 0
crossSize = 'height'
crossStart = 'top'
crossEnd = 'bottom'
}
if (style.flexDirection === 'row-reverse') {
mainSize = 'width'
mainStart = 'right'
mainEnd = 'left'
mainSign = -1
mainBase = style.width
crossSize = 'height'
crossStart = 'top'
crossEnd = 'bottom'
}
if (style.flexDirection === 'column') {
mainSize = 'height'
mainStart = 'top'
mainEnd = 'bottom'
mainSign = +1
mainBase = 0
crossSize = 'width'
crossStart = 'left'
crossEnd = 'right'
}
if (style.flexDirection === 'column-reverse') {
mainSize = 'height'
mainStart = 'top'
mainEnd = 'bottom'
mainSign = -1
mainBase = style.height
crossSize = 'right'
crossStart = 'left'
}
if (style.flexWrap === 'wrap-reverse') {
// 影响交叉轴,reverse 实际上让交叉轴反转了
let tmp = crossStart
crossStart = crossEnd
crossEnd = tmp
crossSign = crossSign * -1
} else {
crossBase = 0
crossSign = crossSign
}
3. 将元素收集进行内(hang)(注意不光是 flex 元素,是所有子元素都先收集进行内,然后再分配空间)
-
- 遍历元素,计算每个元素占据的 mainSpace 和 crossSpace,mainSpace 被占用并递减,crossSpace 选出改行元素中最大 crossSize 的一个作为该弹性行的 crossSpace,如果该弹性行 mainSpace 已经是负数,就换下一行,重置 mainSpace 和 crossSpace,以此类推,直到所有元素分配到行内
- 元素有 flex 或 flex-shrink 属性的情况:元素直接进行内,因为 flex-shrink 默认为 0,所以此时不减小 mainSpace,行内占用空间之后计算
-
- 设置为 no-wrap 的情况:元素直接进行内,正常计算 mainSpace 和 crossSpace,只是不会因为 mainSpace 为负而换行
- 每个 flexLine 都有 mainSpace 和 crossSpace,mainSapce 是减去所有的主轴上元素的 mianSize 的空隙空间,crossSpace 是行高,或行内最高
-
- isAutoMainSize 的情况,是指父元素没有 mainSize 的情况下,通过堆叠子元素的mainSize 来计算一个初始对的 mainSize
// 收集元素进行(hang)(flex-line)
// 父元素没有 mainSize 的情况,需要算出 mainSize
let isAutoMainSize = false
if (!style[mainSize]) {
style[mainSize] = 0
for (let i = 0; i < items.length; i++) {
let item = items[i]
let itemStyle = getStyle(item)
if (itemStyle[mainSize] !== null || itemStyle[mainSize] !== (void 0)) {
style[mainSize] = style[mainSize] + itemStyle[mainSize]
}
}
isAutoMainSize = true;
}
let flexLine = {
items: [],
mainSpace: 0,
crossSpace: 0,
}
let flexLines = [flexLine]
let mainSpace = style[mainSize]
let crossSpace = 0
for(let i =0; i < items.length; i++) {
let item = items[i]
let itemStyle = getStyle(item)
let itemMainSize = itemStyle.flexBasis || itemStyle[mainSize]
if (itemMainSize === null) {
itemMainSize = 0
}
if (itemStyle.flex){
flexLine.items.push(item)
} else if (style.flexWrap === 'nowrap' && isAutoMainSize) {
mainSpace -= itemMainSize
if (itemStyle[crossSize] !== null && itemStyle[crossSize]) {
crossSpace = Math.max(crossSpace, itemStyle[crossSize])
}
flexLine.items.push(item)
} else {
// 当 item 元素的主轴大小 大于 父元素主轴大小,item 的主轴会被改成父元素一样大
if (itemMainSize > style[mainSize]) {
itemMainSize = style[mainSize]
}
if (mainSpace < itemMainSize) {
// 收集行内剩余的 mainSpace 和 crossSpace,之后清空并换行
flexLine.mainSpace = mainSpace
flexLine.crossSpace = crossSpace
flexLine = {
items: [],
mainSpace: 0,
crossSpace: 0
}
flexLines.push(flexLine)
flexLine.items.push(item)
mainSpace = style[mainSize]
crossSpace = 0
continue;
} else {
flexLine.items.push(item)
}
if (itemStyle[crossSize] !== null && itemStyle[crossSize] !== (void 0)) {
crossSpace = Math.max(crossSpace, itemStyle[crossSize])
}
mainSpace -= itemMainSize
}
}
flexLine.mainSpace = mainSpace
if (style.flexWrap === 'nowrap' || isAutoMainSize) {
flexLine.crossSpace = (style[crossSize] !== undefined) ? style[crossSize] : crossSpace
} else {
flexLine.crossSpace = crossSpace
}
4. 根据主轴的几个变量,确定起点偏移量和终点偏移量,确定方向,确定起点,将 Flex 弹性元素放入主轴方向计算,形成 FlexLine 弹性行
-
- mainSpace 小于 0 ,即no-wrap 的情况:
-
-
- flex-shrink 奏效,按照 shrink 的设置比例缩小元素,越大缩的越小,改变了元素的 mainSize,
- 根据父元素的 mainSize 和 mainSpace 可以算出基础缩放系数,通过 flex-shrink 分配基础缩放系数
-
// compute the main axis size, justify-content
if (mainSpace < 0) {
// 只发生在 no-wrap 的单行情况,否则一定会分行出来
// scale 可以通过 shrink 指定,shrink 越大,缩的越小,默认为 1
// flex: 0, 1, auto; grow shrink basis
let flexShrinkTotal = 0
for (let i = 0; i < orderOfItems.length; i++) {
let item = orderOfItems[i];
let itemStyle = this.getStyle(item)
if((itemStyle.flexShrink !== null) && (itemStyle.flexShrink !== void 0)) {
flexShrinkTotal += itemStyle.flexShrink
} else {
flexShrinkTotal += 1
}
}
let scale = style[mainSize] / (style[mainSize] - mainSpace)
let currentMain = mainBase
for (let i = 0; i < orderOfItems.length; i++) {
let item = orderOfItems[i];
let itemStyle = this.getStyle(item)
if((itemStyle.flexShrink !== null) && (itemStyle.flexShrink !== void 0)) {
scale = scale / flexShrinkTotal * itemStyle.flexShrink
}
itemStyle[mainSize] = itemStyle[mainSize] * scale
// 循环计算各个元素在主轴的位置
itemStyle[mainStart] = currentMain
itemStyle[mainEnd] = itemStyle[mainStart] + mainSign * itemStyle[mainSize]
currentMain = itemStyle[mainEnd]
}
}
-
- mainSpace 大于 0
-
- 循环每个 flexLine,分配 mainSpace。此时 mainSpace 的分配有两种形式
-
-
- 有 flex-grow 元素的情况,需要优先分配 mainSpace 给 grow 元素,并且按照 flex-grow 的大小分配,数值越大的元素分配到的等分的 mainSpace 越多,最终会改变元素的 mainSize,然后将剩余空间全部占据
-
-
-
- 按照 justify-content 属性的设置分配 mainSpace,确定空隙 space 的大小以及 mainBase 起始点,循环每个元素的 mainStart 和 mainEnd 偏移量,并结合计算出的空隙 space,以此累加到每行的 mainBase 上,从而使每个元素偏移 mainBase 排版,确定每个在行内的弹性元素的平行行内的位置
-
else if (mainSpace >= 0) {
// 处理每个 flexLine,收集 flex 元素
flexLines.forEach(function (flexLine) {
let mainSpace = flexLine.mainSpace
let flexTotal = 0
for (let i = 0; i < flexLine.items.length; i++) {
let item = flexLine.items[i];
let itemStyle = getStyle(item)
if((itemStyle.flex !== null) && (itemStyle.flex !== void 0)) {
flexTotal += itemStyle.flex
}
}
if (flexTotal > 0) {
// 处理有 flex: 1 属性的元素,即处理 flex-grow
let currentMain = mainBase
for (let i = 0; i < flexLine.items.length; i++) {
let item = flexLine.items[i];
let itemStyle = getStyle(item)
if (itemStyle.flex) {
itemStyle[mainSize] = (mainSpace / flexTotal) * itemStyle.flex
}
// 循环
itemStyle[mainStart] = currentMain
itemStyle[mainEnd] = itemStyle[mainStart] + mainSign * itemStyle[mainSize]
currentMain = itemStyle[mainEnd]
}
} else {
let currentMain = mainBase
let step = 0
if (style.justifyContent === 'flex-start') {
currentMain = mainBase
step = 0
}
if (style.justifyContent === 'flex-end') {
// 起点在 Base,第一个元素需要在空白之后,所以第一个元素在 mainSpace 之后
currentMain = mainBase + mainSpace * mainSign
step = 0
}
if (style.justifyContent === 'flex-center') {
currentMain = mainBase + mainSpace / 2 * mainSign
step = 0
}
if (style.justifyContent === 'space-between') {
currentMain = mainBase
step = mainSpace / (items.length - 1) * mainSign
}
if (style.justifyContent === 'space-around') {
step = mainSpace / items.length * mainSign
currentMain = mainBase + step / 2
}
if (style.justifyContent === 'stretch') {
}
for (let i = 0; i < items.length; i++) {
let item = items[i];
let itemStyle = getStyle(item)
itemStyle[mainStart] = currentMain
itemStyle[mainEnd] = itemStyle[mainStart] + mainSign * itemStyle[mainSize]
currentMain = itemStyle[mainEnd] + step;
}
}
})
}
\
5. 根据交叉轴的几个变量,确定起点偏移量和终点偏移量,确定方向,确定起点,将 FlexLine 放入交叉轴方向计算,形成 FlexBox 弹性盒子
- 根据父元素的 crossSpace 和 align-content 的布局方式确定交叉轴的起始线、以及空隙 space
- 循环加到 crossStart 和 crossEnd 上,并以此类推加到起始线上,偏移起始线,确定每一个弹性行的位置
- 每个弹性行内再根据每行的 crossSpace 和 alignItems 属性,根据弹性元素的大小以及 crossStart 和 crossEnd,偏移每个元素对 crossBase 的位置,计算其垂直行内的位置
- crossBase 再加上每行的 crossSpace ,一直循环到每一行及其行内元素都排版完
计算交叉轴
// compute the cross axis sizes, align-items, align-self
if (!style[crossSize] ) {
// 没有 crossSize 就累加
crossSpace = 0
style[crossSize] = 0
for (let i = 0; i < flexLines.length; i++) {
style[crossSize] += flexLines[i].crossSpace
}
} else {
// 有 crossSize ,crossSpace 就等于 crossSize 减去每行的 crossSpace,得到空隙的空间
crossSpace = style[crossSize]
for (let i = 0; i < flexLines.length; i++) {
crossSpace -= flexLines[i].crossSpace;
}
}
if (style.flexWrap === 'wrap-reverse') {
crossBase = style[crossSize]
} else {
crossBase = 0
}
// 交叉轴大小
// let lineSize = style[crossSize] / flexLines.length
let step
if (style.alignContent === flexStart) {
crossBase += 0
step = 0
}
if (style.alignContent === center) {
crossBase += crossSign * crossSpace / 2
step = 0
}
if (style.alignContent === flexEnd) {
crossBase +=crossSign * crossSpace
step = 0
}
if (style.alignContent === spaceBetween) {
crossBase += 0
step = crossSpace / (flexLines.length - 1)
}
if (style.alignContent === spaceAround) {
step = crossSpace / (flexLines.length)
crossBase += crossSign * step / 2
}
flexLines.forEach(function (flexLine) {
let lineCrossSize = style.alignContent = stretch
// 各行均匀分配
? flexLine.crossSpace + crossSpace / flexLines.length
: flexLine.crossSpace
for (let i = 0; i < flexLine.items.length; i++) {
const item = flexLine.items[i];
let itemStyle = getStyle(item)
// alignItems 是全局的,alignSelf 是只作用于本身的,alignSelf 优先级较高
let align = itemStyle.alignSelf || style.alignItems
if (itemStyle[crossSize] == null) {
itemStyle[crossSize] = (align === stretch)
? lineCrossSize
: 0
}
if (align === 'flex-start') {
itemStyle[crossStart] = crossBase
itemStyle[crossEnd] = itemStyle[crossStart] + crossSign * itemStyle[crossSize]
}
if (align === 'flex-end') {
itemStyle[crossEnd] = crossBase + crossSign * lineCrossSize
itemStyle[crossStart] = itemStyle[crossEnd] - crossSign * itemStyle[crossSize]
}
if (align === 'center') {
itemStyle[crossStart] = crossBase + crossSign * (lineCrossSize - itemStyle[crossSize]) / 2
itemStyle[crossEnd] = itemStyle[crossStart] + crossSign * itemStyle[crossSize]
}
if (align === 'stretch') {
itemStyle[crossStart] = crossBase
itemStyle[crossEnd] = crossBase + crossSign
* (
(itemStyle[crossSize] !== null) && (itemStyle[crossSize] !== (void 0))
? itemStyle[crossSize]
: lineCrossSize
)
itemStyle[crossSize] = crossSign * (itemStyle[crossEnd] - itemStyle[crossStart])
}
}
crossBase += crossSign * (lineCrossSize + step)
})
flex-direction
flex-direction 确定了主轴的方向,一共四个方向,row、column、row-reverse、column-reverse
flex-wrap
flex-wrap 给定了交叉轴的方向,可以反转或取消交叉轴
wrap 就是正常流的方向,从左到右,从上到下
wrap-reverse 就是反方向,从下到上,从右到左
justify-content
justify-content 计算的是弹性元素平行于弹性行行内的方向的位置,或者说平行于主轴方向的空隙排布
flex-start 和 flex-end 分别代表主轴的起点和终点,注意 flex-end 会把剩余的空隙放在最前面再计算其他元素的位置
center 表示主轴方向平均分配空隙,二分至起点和终点
space-around 是将空隙除以元素数得到 step-space,并且以 mainBase + step-space 除以 2 的地方为起点
space-between 则还是以 mainBase 为起点,空隙均匀分在每个夹缝里
align-contents
align-content 计算的是弹性行 flexLine 平行于交叉轴的方向上的位置
flex-start 和 flex-end 分别代表交叉轴的起点和终点
center 表示平均分配空隙,二分至起点和终点
stretch 表示所有 flexLine 在交叉轴方向等比放大
space-between 和 space-around 分别表示空隙均匀分在每个夹缝里,以及将空隙除以元素数得到 step-space,并且以 crossSpace + step-space 除以 2 的地方为起点
align-items
align-content 计算的是弹性行 flexLine 平行于交叉轴的方向上的位置
flex-start 和 flex-end 分别代表在行内的起点和终点
center 表示在行内平均分配空隙,二分至起点和终点
stretch 表示所有元素在行内的交叉轴方向等比放大
baseline 表示所有元素在行内的以文字基线位置为标注对齐
align-self
与 align-items 同理都是控制行内空隙排布,区别在于 align-self 只作用于被设置的单个 flex 子元素上,默认值 auto,继承 align-items 的属性,但优先级比 align-items 高
\
flex-shrink
flex-shrink 决定了当主轴空隙为负,即元素溢出时,元素主轴方向是否缩放以及缩放的大小,数字占比越大缩小的越多,值必须是自然数。下图元素 2 表示了 flex-shrink 为 0 就不收缩,其余元素都是 flex-shrink 默认为 1 的元素都等比例收缩
flex-grow
flex-shrink 决定了当主轴空隙为正,即行内有空间时,元素主轴方向是否缩放以及缩放的大小,数字占比越大放大的越大,值必须是自然数
flex-basis
flex-basis 指的是元素占用的主轴的空间,默认值是 auto,即元素本来的大小
order
order 属性设置了排版时的顺序。数字越小排的靠前,值可以为负数。
\
通过应用理解
比如我们现在设置一个 flex container,里面有若干个 flex item
首先获取 mainSize 和 crossSize,默认是 width 和 height,然后排版起点默认以 flex-start ,也就是以父元素最左边为 mainBase 起点,从左向右排版为正方向,mainSign 为 +1,表示元素是递增偏移 mainBase
在排版开始后,元素入行,mainBase 偏移并递减 mianSpace,直到 mainSpace 为负数,就开新行,flex line 记录下每行的 mainSpace,根据每行最大的 crossSize 确定 flex line 的 crossSize
循环每个 flex line,根据 mainSpace 和决定分配 mainSpace 的 justify-content 属性,决定如何排序 flex 子元素和普通子元素以及空隙
循环每个 flex line,根据 crossSpace 和决定分配 crossSpace 的 align-content 属性,决定每行的垂直排序起点。接着再根据 align-items 分配每行的起点开始的空隙,完成每行的垂直排序,最终完成垂直排版
结合 Cocos
Cocos 拖拽模板是为了拖入多个拖拽选项而准备的,拖拽到里面的选项可能大小不一,为了使得选项能够正确排版,就想到了 flex 布局结合到 cocos 里面的方式,在这个过程中发现 flex 布局引入 cocos 是时需要考虑锚点、xy轴方向等问题
锚点的问题:
cocos 默认是正中心 (0.5, 0.5),浏览器内默认是左上角(0,1)
可以通过计算定位偏移量解决,就在计算实际 cc.Vec2 的时候用 mainStart 和 mainSize 结合计算出中心点即可
xy 轴方向的问题:
cocos 默认是 xy 轴正方向都是向上向右,浏览器内默认 xy 轴正方向是向下向右
可以通过对初始化数值的 crossSign 和 mainSign 配置解决,即想要从哪个方向排版,就将 mainSign 和 crossSign 改为对应方向的符号,比如 flexDirection 为 row 的情况
注意
目前因为考虑到 Scale 值影响动画原因以及没有缩放子节点的需求,因此需要去掉 flex-shrink 和 flex-grow 及类似的属性,同时对溢出的部分进行空间分配,使得溢出时布局仍然正确
最终返回的是一组 cc.Vec2 数据,根据 id 对其对应的元素进行分配位置,重新移动和排版
需要用到父元素偏移量等手段影响实际的布局,就在计算出的 cc.Vec2 加上父元素及设定的偏移量
实现上的细节
- mainBase 的计算依靠 flex-direction,一般就是 0 或 mainSize
- step 或者说 space 的计算(空隙),space-between 是均匀分布空隙,mainSpace 除以元素数减 1,space-around 是起点终点各加上一个 step space 除 2
- flex-flow 是 flex-direction 和 flex-wrap 的简写 row、no-warp
- flex 是 flex-grow、flex-shrink、flex-basis 的简写,默认值 0、1、auto
- 当没有设定 width 时,需要计算 主轴的 mainSize 并主动把主轴方向的父元素撑大,代码里的isAutoMainSize 就处理了这个情况,isAutoMainSize 有点类似于 no-wrap 的情况
- crossSize 的计算,和 flexLine 有关,如果没有初始值,会变成各个 flexLine 的累计值,同 isAutoMain
- crossBase 的计算,step 计算,crossSpace 的计算类似于上面 mainBase 等的计算
- stretch 比较特殊,justify-content 和 align-items 中的 stretch 可以改变元素 mainSize 和 crossSize 的大小
实现的效果
总结
通过将实际的属性抽象成 flex 里的主轴和交叉轴上的属性, flex 布局成为了一套足够简单和灵活的布局方式,最终开放给用户的 API 也足够简单易懂,能够让人快速上手并且做出符合需求的布局效果。通过实现和还原 flex 布局过程,能够更加深入的了解到 flex 布局的逻辑,以及为什么 flex 能够覆盖多重的布局场景
参考文章:
- developer.mozilla.org/zh-CN/docs/… 《Basic_Concepts_of_Flexbox》
- developer.mozilla.org/zh-CN/docs/… 《Controlling_Ratios_of_Flex_Items_Along_the_Main_Ax》
- www.ruanyifeng.com/blog/2015/0… 《Flex 布局教程:语法篇》