Web layout 是Web UI中的基础架构, 重要性不言而喻. 传统的盒模型, 借助display, position, float 属性应对普通布局游刃有余, 但针对复杂的或自适应布局, 常常捉襟见肘. 比如垂直居中, 就是一个老大难的问题, 借助flex弹性盒模型, 两行代码就可以优雅的实现之. (该方法曾在 16种方法实现水平居中垂直居中 一文中提到). 当然, 本次我们不会只讨论垂直居中的问题, 我将努力尽可能的还原flex的应用场景.
原文: 弹性盒模型Flex指南
本文将介绍flex子项目压缩比计算, 多层flex嵌套的常见问题. 通读本文, 你还将了解如下内容:
Flex
Flex即弹性盒模型, 该布局方案由W3C于2009年提出. 此后, Flex方案便历经v2009, v2011, v2012, v2014, v2015, v2016等版本, 最近方案是2016年5月26日起草的 CSS Flexible Box Layout Module Level 1.
兼容性
首先, 我们来回顾下如今PC端的兼容性(以下为完全兼容版本).
| IE | Edge | Firefox | Chrome | Safari | Opera |
|---|---|---|---|---|---|
| - | 12+ | 28+ | 21+ | 6.1+ | 12.1+ |
以上, IE10+仅支持2012版W3C的flex语法, 且存在较多已知的bug, 此时使用flex布局需谨慎.
Chrome浏览器v21~v28版本需要添加 "-webkit-" 前缀.
Safari浏览器v6.1~v8版本需要添加 "-webkit-" 前缀.
Opera浏览器v15~v16版本需要添加 "-webkit-" 前缀.
因此, 看到一些sass编译后的css文件中带有 "-webkit-" 前缀无需惊慌.
平时开发时最为担心的便是移动端兼容性, 请看:
| IOS Safari | Opera mini | Android | Android Chrome | UC | 微信 |
|---|---|---|---|---|---|
| 7.1+ | √ | 4.4+ | 55 | - | 当前支持 |
微信当前版本已支持flex.
UC不对外提供webview内核, 除去一些H5app的应用, 各种分享页基本(常在微信下打开)基本不需要担心对其兼容性, 实在需要实现, UC还是支持老版本的弹性盒子的, 可以优雅降级. 可见, Android4.4以上基本可以安心使用flex.
Autoprefixer
强记各种浏览器的前缀是没有必要的, 因为autoprefixer该做的, 都帮我们做了. 因此建议尝试下以下三个插件之一.
优势
Flex布局使得子项目能够"弹性"的改变其高宽, 自由填充容器剩余空间, 以适应容器变大, 或者压缩子项目自身, 以适应容器变小; 同时还可以方便的调节子项目方向和顺序. flex常用于高宽需要自适应, 或子项目大小成比例, 或水平垂直对齐等场景.
概念铺垫
Flex弹性盒模型里, 有容器和项目之分. 设置display:flex的为容器, 容器内的元素称作它的子项目, 容器有容器的一套属性, 子项目有子项目的另一套属性. (可以这么理解: father作为弹性盒子, 制定行为规范, son享受盒子的便利, 按照规范划分各自的"辖区").
以下图片摘自大漠的一个完整的Flexbox指南文中.

father制定的规范, 基于两个方向 — 水平和垂直.
- 水平方向的称之为主轴(main axis), 垂直方向的称之为交叉轴(cross axis).
- 主轴起始位置, 叫做
main start, 末尾位置叫做main end; - 交叉轴起始位置, 叫做
cross start, 末尾位置叫做cross end. - 子项目在主轴上所占的宽(高)度, 叫做
main size, 在交叉轴上所占的高(宽)度, 叫做cross size.
属性
display: flex | inline-flex;(元素将升级为弹性盒子). 前者容器升级为块级盒子, 后者容器将升级为行内盒子. 元素采用flex布局以后, 子元素的float, clear, vertical-align属性都将失效.
容器属性
容器具有以下6个属性.
- flex-direction 指定主轴的方向.
| flex-direction的值 | 描述 |
|---|---|
| row(默认) | 指定主轴水平, 子项目从左至右排列➜ |
| row-reverse | 指定主轴水平, 子项目从右至左排列⬅︎ |
| column | 指定主轴垂直, 子项目从上至下排列⬇︎ |
| column-reverse | 指定主轴垂直, 子项目从下至上排列⬆︎ |
- flex-wrap 指定如何换行.
| flex-wrap的值 | 描述 |
|---|---|
| nowrap(默认) | 默认不换行 |
| wrap | 正常换行 |
| wrap-reverse | 换行, 且前面的行在底部 |
- flex-flow 它是flex-direction 和 flex-wrap的简写形式, 默认值为
row nowrap. - justify-content 指定主轴上子项目的对齐方式.(通常为水平方向对齐方式)
| justify-content的值 | 描述(子项目--主轴方向) |
|---|---|
| flex-start(默认) | 子项目起始位置与main start位置对齐 |
| flex-end | 子项目末尾位置与main end位置对齐 |
| center | 在主轴方向居中于容器 |
| space-between | 与交叉轴两端对齐, 子项目之间的间隔全部相等 |
| space-around | 子项目两侧的距离相等, 它们之间的距离两倍于它们与主轴起始或末尾位置的距离. |
- align-items 指定交叉轴上子项目的对齐方式.(通常为垂直方向对齐方式)
| align-items的值 | 描述(子项目—交叉轴方向) |
|---|---|
| flex-start | 子项目起始位置与cross start位置对齐 |
| flex-end | 子项目末尾位置与cross end位置对齐 |
| center | 在交叉轴方向居中于容器 |
| baseline | 第一行文字的基线对齐 |
| stretch(默认) | 高度未定(或auto)时, 将占满容器的高度 |
- align-content 指定多根主轴的对齐方式. 若只有一根主轴, 则无效.
| align-content的值 | 描述(子项目) |
|---|---|
| flex-start | 顶部与cross start位置对齐 |
| flex-end | 底部与cross end位置对齐 |
| center | 在交叉轴方向居中于容器 |
| space-between | 与交叉轴两端对齐, 间隔全部相等 |
| space-around | 子项目两侧的距离相等, 它们之间的距离两倍于它们与主轴起始或末尾位置的距离. |
| stretch(默认) | 多根主轴上的子项目充满交叉轴 |
子项目属性
子项目具有以下6个属性.
flex-grow 指定子项目的放大比例, 默认为0(即不放大). 该属性可取值为任何正整数. 假设各个子项目的放大比例之和为n, 那么容器内剩余的空间将分配n份, 每个子项目各自分到x/n份. (x为该子项目的放大比例)
flex-shrink 指定子项目的缩小比例, 默认为
1. 设置为0时, 空间不足该子项目将不缩小. 我们知道,容器的缩小总宽度=子项目所需要的总宽度-容器实际宽度, 假设容器需要缩小的宽度为W, 某子项目的默认宽度为L, 其缩小比例为p, 那么该子项目实际的宽度为L-p*W.上面轻描淡写的给出了子项目的缩小比例, 可能会给你一种错觉— "缩小比例很容易计算", 实际上, 我们在计算元素需要缩小比例时, 总是要考虑到元素自身默认的大小.
假设上述子项目其flex-shrink值为x1, 另一个子项目的默认宽度为R, flex-shrink值为x2, 考虑到元素自身大小. 最终第一个子项目的缩小比例是加权了自身默认大小后的结果, 即
rate = L*x1/(L*x1 + R*x2).
为什么计算会如此复杂, 如此不直观??? 这是因为, 子项目的大小各不一致, 假如一个子项目是另一个子项目主轴宽度的9倍, 前者的flex-shrink值为1, 后者为9, 而容器实际上只有他们默认总宽度的一半. 这意味着, 这两个子项目共计要压缩为默认的一半. 如果仅仅按照flex-shrink值来决定比例, 那么第二个子项目需要压缩其默认的9/10, 而我们知道, 它默认是如此的小, 即使全部压缩了, 也无济于事; 而第一个元素仅需要压缩其默认的1/10, 简直就是九牛一毛, 根本达不到默认总宽度压缩一半的效果. 很明显, 这种压缩比例的分配方式是不合理的. 因此最终的压缩比例加入了默认宽度值(即flex-basis值), 表达式的分子为
flex-shrink * flex-basis, 分母为各子项目flex-shrink * flex-basis之和.
flex-basis 指定子项目分配的默认空间, 默认为
auto. 即该子项目的原本大小.flex 是 flex-grow, flex-shrink, flex-basis 3个属性的缩写. 默认为
0 1 auto. 该属性取值为auto时等同于设置为1 1 auto, 取值为none时等同于设置为0 0 auto.align-self 指定单个子项目独立的对齐方式. 默认为
auto, 表示继承父元素的align-items属性, 如无父元素, 则等同于stretch. 该属性共有6种值, 其他值与上述align-items属性保持一致.order 指定子项目的顺序, 数值越小, 顺序越靠前, 默认为
0.
flex属性的优先级
我们可以给input设置flex:1, 使其充满一行, 并且随着父元素大小变化而变化. 也可以给div设置flex:1使其充满剩余高度.
使用flex布局这些都不是难事, 需要注意的是, 这其中有坑. 为了避免踩坑, 我们先来看下flex属性的优先级:
width|height > 自适应文本内容的宽度或高度 > flex:数值
这意味着, 首先是元素宽高的值优先, 其次是内容的宽高, 再次是flex数值. 现在我们来看看坑是什么.
- 给input元素设置
flex:1时需要注意, 通常input拥有一个默认宽度(用于展示默认数量的字符), 在chrome v55下, 这个宽度默认为126px(同时还包含2px的border). 因此想要实现input宽度自适应, 可以设置其width为0. - 给div元素设置
flex:1时, 因div的高度会受子级元素影响, 为了使得该div占满其父元素剩余的高度, 且不超出, 建议将该div的height属性设置为0.
场景回顾
想要实现垂直居中的效果, 只需要设置父元素为
display:flex;justify-content:center即可. (当然, 父元素样式采用:display:table;, 子元素样式采用:display:table-cell;vertical-align:middle也是可以实现的), 如下图.
想要实现左右两个元素等高(父元素高度由子元素撑开), 并且各占一半的宽度. 如上图.
- 早期的实现方案, 需要借助负margin. 父元素样式设置为
overflow:hidden, 子元素样式设置为margin-bottom:-10000px;padding-bottom:10000px;, 这样, 每个子元素便能借助padding撑开, 同时, 借助负margin和overflow合理裁剪. - 第二种方案就是借助IE8都支持的
display:table属性, 父元素样式设置为display:table, 子元素设置为display:table-cell. 利用表格的行高一致性, 轻松实现行高一致. - 最终, 我们发现, 还是flex弹性盒模型来得方便快捷, 它只需要父级元素样式设置为
display:flex.
- 早期的实现方案, 需要借助负margin. 父元素样式设置为
有关flex的旧语法, 请戳这篇回顾 Flex布局新旧混合写法详解(兼容微信) .
有关移动端的最佳实践, 请戳这篇围观 移动端全兼容的flexbox速成班 .
当然, 这里还有一个 Flexbugs 列表, github上已有近6k的star, 感兴趣可以前去看看.
本问就讨论这么多内容,大家有什么问题或好的想法欢迎在下方参与留言和评论.
本文作者: louis
本文链接: louiszhai.github.io/2017/01/13/…
参考文章