弹性盒模型Flex指南

目录
  1. 导读
  2. Flex
  3. 兼容性
  4. Autoprefixer
  5. 优势
  6. 概念铺垫
  7. 属性
    1. 容器属性
    2. 子项目属性
  8. flex属性的优先级
  9. 场景回顾

导读

Web layout 是Web UI中的基础架构, 重要性不言而喻. 传统的盒模型, 借助display, position, float 属性应对普通布局游刃有余, 但针对复杂的或自适应布局, 常常捉襟见肘. 比如垂直居中, 就是一个老大难的问题, 借助flex弹性盒模型, 两行代码就可以优雅的实现之. (该方法曾在 16种方法实现水平居中垂直居中 一文中提到). 当然, 本次我们不会只讨论垂直居中的问题, 我将努力尽可能的还原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指南文中.

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数值. 现在我们来看看坑是什么.

  1. 给input元素设置flex:1时需要注意, 通常input拥有一个默认宽度(用于展示默认数量的字符), 在chrome v55下, 这个宽度默认为126px(同时还包含2px的border). 因此想要实现input宽度自适应, 可以设置其width为0.
  2. 给div元素设置flex:1时, 因div的高度会受子级元素影响, 为了使得该div占满其父元素剩余的高度, 且不超出, 建议将该div的height属性设置为0. 此时PC端表现非常优秀,美中不足的是,对于移动端而言,div的子元素设置为height:100%并没有什么卵用,此时子元素高度依然为0。目前我能想到的比较好的解决方案就是:给div也设置display:flex;align-item:stretch,使得div本身也获得flex布局能力,同时div子元素高度充满div本身。

场景回顾

  1. 想要实现垂直居中的效果, 只需要设置父元素为display:flex;justify-content:center 即可. (当然, 父元素样式采用:display:table;, 子元素样式采用:display:table-cell;vertical-align:middle 也是可以实现的), 如下图.

  2. 想要实现左右两个元素等高(父元素高度由子元素撑开), 并且各占一半的宽度. 如上图.

    • 早期的实现方案, 需要借助负margin. 父元素样式设置为overflow:hidden, 子元素样式设置为margin-bottom:-10000px;padding-bottom:10000px;, 这样, 每个子元素便能借助padding撑开, 同时, 借助负margin和overflow合理裁剪.
    • 第二种方案就是借助IE8都支持的display:table属性, 父元素样式设置为display:table , 子元素设置为display:table-cell. 利用表格的行高一致性, 轻松实现行高一致.
    • 最终, 我们发现, 还是flex弹性盒模型来得方便快捷, 它只需要父级元素样式设置为display:flex.
  3. iphone低版本下,flex与inline-block有兼容性问题,这将导致inline-block的元素脱离flex布局(就好像其父元素没有设置为flex布局一样)。目前没有什么比较好的解决方案,建议在flex布局下慎用inline-block元素或者将其指定为block元素。

有关flex的旧语法, 请戳这篇回顾 Flex布局新旧混合写法详解(兼容微信) .

有关移动端的最佳实践, 请戳这篇围观 移动端全兼容的flexbox速成班 .

当然, 这里还有一个 Flexbugs 列表, github上已有7k+的star, 感兴趣可以前去看看.


本文作者: louis

本文链接: http://louiszhai.github.io/2017/01/13/flex/

参考文章

Fork me on GitHub