前言
flex 布局在目前前端开发中使用到的概率还是蛮大的,尽管它从诞生到现在已经经历了 N 年了,但是从个人的工作圈子来看,有很多人其实对 flex 布局的理解还是很表面,比如一味地使用 flex: 1 或者 flex: auto 等,导致一旦出现一些样式问题或者浏览器兼容问题,马上就无法处理了。
因此,个人从 flex 布局基础知识和浏览器兼容性两个方面,结合个人工作经验,整理了一篇文章,希望对大家有帮助,如有阐述有误之处,还望不吝指教!
文章大体分为两个部分,第一块是 flex 的基础知识,包括布局特性、属性特征等,相对偏理论些。第二块是 flex 的浏览器兼容问题总结,以及 flex 使用建议,这源于个人工作场景中实际碰到的问题,经尝试解决后的总结,偏实际应用一点。
如果对 flex 布局原理比较了解,但遇到浏览器兼容问题的同学来说,可以直接看第二块内容。
flex 布局 - 一维布局
flexbox 是一种一维的布局,因为一个 flexbox 一次只能处理一个维度上的元素布局,一行或者一列
flex 布局 - 容器属性
display: flex vs display: inline-flex
相同点
- 对于容器内部的子元素而言,没有任何区别
差异
- 对于容器本身而言,
flex
表现为 块级元素;inline-flex
表现为 块级内联元素- 因此未给容器设置宽度时,
flex
容器默认宽度为100%(一行占满);inline-flex
默认宽度会根据子元素的宽度去自适应
- 因此未给容器设置宽度时,
flex-direction
轴线 - 主轴 & 交叉轴
- 主轴由
flex-direction
定义,可取row
(横向主轴)、column
(纵向主轴)等值。 - 交叉轴则为与主轴垂直的轴线。
flexbox
的特性是沿着主轴或者交叉轴对齐之中的元素。
起始线 & 终止线
起始线、终止线用于描述 flexbox
子元素的书写(排列)方向。
当主轴为 row(横向主轴)时:
- 书写英文时,主轴起始线为左边;书写阿拉伯文时,主轴起始线为右边。
- 交叉轴起始线是
flexbox
的顶部,终止线是底部,因为两种语言都是水平书写模式。
当主轴为 column(纵向主轴)时:
- 主轴起始线为
flexbox
的顶部,终止线为flexbox
的底部,因为两种语言都是水平书写模式。 - 书写英文时,交叉轴起始线为左边;书写阿拉伯文时,交叉轴起始线为右边。
属性值与轴线、起始终止线的关系
假定以 row 为基准,那么取其他属性时的线位变化如下:
- row-reverse:主轴 & 交叉轴不变;起始线 & 终止线 改变
- 主轴的起始线、终止线互换;交叉轴的起始线、终止线不变
- column:主轴 & 交叉轴互换;起始线 & 终止线 改变
- 主轴的起始终止线,与交叉轴的起始终止线互换
- column-reverse:主轴 & 交叉轴互换;起始线 & 终止线 改变
- 主轴的起始终止线,与交叉轴的起始终止线互换
- 交叉轴的起始线、终止线互换;主轴的起始线、终止线不变
justify-content
使子元素在主轴上对齐
- 初始值是
flex-start
,子元素从容器的起始线开始排列。 flex-end
,元素从容器终止线开始排列center
,居中排列,每个元素紧邻space-between
,占满容器,并且元素之间间隔相等space-around
,占满容器,并且每个元素的左右空间相等
align-items
使子元素在交叉轴上对齐
- 初始值是
stretch
- 未定义容器高度,容器会被拉伸到最高元素的高度;
- 定义了容器高度,元素会被拉伸(收缩)到容器高度。
flex-start
,按容器的起始线对齐flex-end
,按容器的终止线对齐center
,居中对齐
align-content
使子元素在交叉轴方向对齐,但为多行对齐
- 默认值是
stretch
,剩余空间被所有行平分,以扩大它们的交叉轴尺寸 flex-start
,所有行从容器的起始线排列flex-end
,所有行从容器终止线开始排列center
,所有行在容器中间,紧凑排列space-between
,所有行占满容器,并且每行之间间隔相等space-around
,所有行占满容器,并且每行的上下空间相等
flex-wrap
换行方式
- 默认 nowrap,不换行
- wrap,换行
- wrap-reverse,换行反向排列
flex-flow
为 flex-direction
和 flex-wrap
的合并写法
flex 布局 - 子元素属性
子元素默认样式
- flex-direction: row,元素排列为一行,并从主轴的起始线开始
- flex: 0 1 auto,元素不会在主轴方向拉伸,但会随可用空间会缩小
- flex-wrap: nowrap,如果有太多元素超出容器,它们会溢出而不会换行
- 如果一些元素比其他元素高,那么元素会沿交叉轴被拉伸来填满它的大小
flex-basis
元素的初始(基准)空间大小
- 默认值是
auto
- 元素设置了宽度,
flex-basis
为设置的宽度 - 元素未设置宽度,
flex-basis
为元素内容的尺寸
- 元素设置了宽度,
flex-basis
属性优先于width
属性;- 设为 0 ,则子元素的大小不在空间分配计算的考虑之内
flex-grow
元素沿主轴方向的扩张尺寸,会占据主轴上的可用空间
- 按比例分配空间(默认为 0 ),扩张值为
flex-basis
基准乘以flex-grow
扩张比例- 设置一样的值,则平分可用空间
- 设置不同的值,按比例平分可用空间
下图为不同 flex-basis
的情况下,flex-grow
均设为 1 的场景:
flex-shrink
元素沿主轴方向的收缩尺寸,只有在子元素总和超出主轴才会生效
- 按比例分配空间(默认为 1 ),收缩值为
flex-basis
基准乘以flex-shrink
收缩比例 - 随着盒子越来越小,小的子元素最终会以
min-content
的大小进行铺设,后续空间会一直从大的子元素中移除- 所谓
min-content
,即当前容器内部最小的不可断行元素的宽度- 下图 largeSizeContentWithoutWrap 是连续不可断行的,所以无法再压缩空间
- 下图 middle-size-content-width-split-code 可以以横杠 - 断行,所以可以换行以压缩空间
- 下图 content-content-content-content-content-content 已经被断行到不可再断行的宽度,因此无法再继续换行来压缩空间
- 所谓
下图为不同 flex-basis
的情况下,flex-shrink
均设为 1 的场景:
order
项目的排列顺序
- 数值越小,排列越靠前,默认为 0
align-self
允许单个项目有与其他项目不一样的对齐方式
- 可覆盖
align-items
属性,默认值为auto
- 表示继承父元素的
align-items
属性- 如果没有父元素,则等同于
stretch
- 如果没有父元素,则等同于
几个 flex 样式的简写
flex: initial
相当于 flex: 0 1 auto
flex: auto
相当于 flex: 1 1 auto
flex: none
相当于 flex: 0 0 auto
flex: 1
相当于 flex: 1 1 0
flex: auto 与 flex: 1 的区别
flex: 1 1 auto
- 在各元素初始宽度基础上,平均分配可用空间
- 各元素宽度不相等,初始宽度大的分配后宽度也大
flex: 1 1 0
- 在各元素 0 宽度的基础上,平均分配可用空间
- 各元素宽度相等
flex 布局 - 浏览器兼容性
浏览器兼容性概览
- 红色部分为不支持 flex 规范
- 黄色部分为只支持老的 09 版 flex 规范(display: box; )
- 绿色部分为完全支持新的 12 版 flex 规范(display: flex; )
flex 规范,09 版 vs 12 版
支持少数低版本浏览器的方案
- 联合使用支持 09 版和 12 版的 flex 布局
.container {
display: box;
display: flex;
}
注意
- 浏览器兼容语法(-webkit- 等)可以通过postcss 等工具实现
- 但 09 版 flex 不支持很多特性
chrome 49 的兼容性问题
问题描述
父元素 flex: 1
,子元素 height: 100%
,因内容较少无法填充满父元素
原因
父元素未设置 height
,故子元素获取不到父元素的 height
解决方案一
通过父元素 absolute
,子元素 relative
,这样子元素的高度就会根据父元素的高度进行计算
解释
规范中有提到,如果包含块的高度没有显式地指定(高度由内容决定),并且不是绝对定位元素,则计算值为 auto,高度和百分比值是没办法进行计算的! auto * 100/100 = NaN
缺陷
子元素 absolute
带来的影响,比如需要再设置 width: 100%
解决方案二
子元素不使用 height: 100%
,而使用 flex-grow
来占满空间
解决方案三
父元素不使用 flex: 1
,而使用 display: flex;
+ height: 100%;
解释
应用于 display: flex
的元素,使其成为 flex 容器。
这会自动设置 align-items: stretch
,会告诉 child(.item)扩展父级的完整高度。
总结
Chrome49
浏览器针对子元素设置 height: 100%
后因内容较少无法填充满父元素的情况,
建议父元素使用 display: flex;
+ height: 100%;
chrome 79 以上的兼容性问题
问题描述
当父元素设置 flex: 1
填充满容器,子元素设置 height: 100%
后,子元素内容过多会超出父元素
解决方案
对于一个设置了 flex: 1
的元素,再对其设置 min-height:0
,保证内容不超出外层容器
解释
父元素设置 min-height: 0
相当于告诉子元素父元素 height > 0
,子元素可以由此间接地拿到父元素的高度,然后设置 height: 100%
保持父元素同样的高度,避免溢出
注意
要兼容 chrome49
的话还需使用 display: flex;
+ height: 100%;
替换 flex: 1
总结
Chrome79
以上浏览器,针对子元素设置 height: 100%
因内容过多超出父元素的情况,建议父元素使用 min-height: 0;
(Chrome 79) + display: flex;
+ height: 100%;
(Chrome 49)
chrome 49 与 chrome 79 以上的另一个差异点
差异描述
- flexbox 一个子元素设置了 flex,另一个子元素设置了很高的高度
- 当 flexbox 高度不足以容纳两者时,两者高度都会受到压缩
- 但在 chrome49 下设置了 flex 的子元素,会被压缩到连其子元素都无法完整展示
- 而 chrome79 下设置了 flex 的子元素,则会尽可能保留其子元素的展示
chrome 49
chrome 79 以上
解决方案
若要保持设置了 flex 的子元素的完整展示,最好设置 flex 的同时设置 flex-shrink: 0;
flex 布局使用建议
针对横向布局
按照一般写法即可
.container {
display: flex;
flex-direction: row;
}
针对纵向布局
需要增加兼容 chrome49 以及 chrome79 以上的样式
.container {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
针对子元素
视情况灵活使用 flex-basis
、flex-grow
、flex-shrink
的组合,而不是一味地全部使用 flex:1
或者 flex: auto
最后
附上案例项目的 git 地址:(案例可能写的不是很好-0-)
https://github.com/hezhikai/blog-flex_compatible.git
重申几点:
- 上面 flex 布局的基础知识是基于自己看过的很多文档归纳的,可能有些描述摘录了其他地方的文章的用词或片段,如有侵权请告知,我会删掉。
- 因为整理的时候查的资料比较多,现在很多地方已经想不起来参考了哪里的文章,所以不大好去附加参考资料,请见谅。
- 后面 flex 布局的兼容性问题,更多的是基于工作场景遇到并解决后整理的。当然也有查各方面资料(虽说目前网上这块的资料不够详尽),但是也有个人经验的总结在里面。
- 因此后面如果个人再遇到其他 flex 的浏览器兼容性问题,还会再总结补充进来。
- 虽说这篇文章不能算是完全原创,但是毕竟也是个人花了时间和精力整理、基于实际工作场景尝试和总结的,因此如需转载,还请附上此出处哈!
- 也算是对个人总结的一些认可吧 -.-