Flexbox 是一种 CSS 布局机制,可以说是目前浏览器原生支持的最好、使用最广泛的布局机制了。本文通过一些例子来说明 Flexbox 布局的工作原理,可以让我们更好的使用 Flexbox。

与 CSS Grid 可以同时在横向和纵向两个方向进行布局不同,Flexbox 只能在单一方向上进行布局,即要么横向,要么纵向。所谓布局,其实就是空间的分配过程,也就是说计算元素尺寸和容器剩余空间尺寸的过程。

Flexbox 的布局原理

整个布局过程我们可以简单的总结如下:

  1. 计算 flex 容器内的可用空间。整个容器的尺寸减去容器的 border、padding 等所得的剩余空间尺寸。
  2. 计算每个 flex 元素的 flex base 尺寸和元素的假设尺寸。具体计算方法是取 flex-basis、min-width 和 flex 元素内容尺寸的较大者。flex base 尺寸是 flex 元素需要的最小尺寸,这个尺寸不能小于元素内容的尺寸。元素的假设尺寸是指在 flex 因子生效前元素的尺寸,flex 因子生效后可能导致元素发生伸缩。
  3. 计算容器内所有 flex 元素的假设尺寸总和。
  4. 将所有元素的假设尺寸总和与容器内可用空间尺寸做比较,来确定 flex 因子,也就是说当假设尺寸总和超过容器内可用空间尺寸时,使用 flex-shrink,否则使用 flex-grow。在同一时间,flex-shrink 和 flex-grow 只有一个生效。

所谓 flex 因子,简单来说就是缩小和放大。浏览器在进行 flexbox 布局时会先确定使用哪种 flex 因子,然后再根据选用的 flex 因子来对元素尺寸进行调整。

在进行调整的时候,就会涉及到一个剩余空间的计算问题。如果 flex 元素明确指定了尺寸大小(definite size,比如设置了 width),那么这个元素就是不可伸缩的。如果没有显式指定尺寸,则会按照上面第2步那样计算假设尺寸。剩余空间的尺寸就是容器内的可用空间尺寸减去这些元素的尺寸之和。

例子说明

我们有如下 dom 结构:

<div id="flex">
<div id="a">Antidisestablishmentarianism</div>
<div id="b">B</div>
<div id="c">Cherries jubilee</div>
<div id="d">D</div>
<div id="e">E</div>
</div>

样式如下:

[id=flex] {
font-weight: 300;
display: flex;
outline: 1px dashed #555;
width: 1200px;
margin: 3rem auto;
}
/*其他样式已省略*/

页面展示效果如下:

我们没有设置元素的 flex 样式属性,默认值是 0 1 autoflex 样式属性是 flex-growflex-shrinkflex-basis 这三个样式属性的简写形式。0 1 auto 分别对应为 flex-growflex-shrinkflex-basis 的值。

通过取值可以看到,因为我们禁止了放大和收缩,并且 flex-basis 的值是 auto,浏览器就使用元素的最大内容尺寸来计算所有元素的尺寸总和,比容器的尺寸(1200px)小,所有会有额外的剩余空间。

关于 flex 这个样式属性,我们额外做一些说明。flex 属性可以接收最少一个、最多三个属性值。

当只有一个属性值的时候,flex 的工作模式是这样的 <number> 1 0。即 flex: 2 最终的结果是 flex: 2 1 0

当有两个属性值的时候,第一个值会被解析为 flex-grow,第二个值如果是数字,则会被解析为 flex-shrink,如果是一个合法的宽度值则会被解析为 flex-basis。即 flex: 1 0 解析为 flex: 1 0 0,而 flex: 1 20rem 则被解析为 flex: 1 1 20rem

当有三个属性值的时候,第一个值被解析为 flex-grow,第二个值必须为数字,且会被解析为 flex-shrink,第三个值则必须是合法的宽度值,被解析为 flex-basis

设置 flex-grow

我们增加如下样式设置:

[id=flex] > div {
flex: 1;
}

实际上等同于 flex: 1 1 0,即 flex-grow: 1。从前面的例子我们可以看到,所有元素的假设尺寸之和是小于容器剩余空间尺寸的,所以浏览器会使用 flex-grow 来作为 flex 因子,因为我们设置了 flex-grow: 1 ,因此浏览器会等比放大所有的元素。如下图:

实际上浏览器会循环通过下面的公式来计算每个元素的最终尺寸:

当前元素伸缩值 = (当前剩余空间 - 所有剩余元素的 flex-grow 值的和) * 当前元素的 flex-grow 值

在上面的例子中,容器的剩余空间为 1200px,因此,通过公式计算:

( 1200 ÷ ( 1 + 1 + 1 + 1 + 1 ) ) × 1 = 240

元素 A 的最终尺寸为 240px + flex-basis = 240px + 0 = 240px。但是,因为 Antidisestablishmentarianism 这个单词比较长,实际会占用 417px 的空间大小,因此元素 A 的最终尺寸为 417px

此时,在计算元素 B 的尺寸时,剩余空间为 1200px - 417px = 783px。元素 B 的伸缩值为:

( 783 ÷ ( 1 + 1 + 1 + 1 ) ) × 1 = 195.75 0 + 195.75 = 195.75

同理,元素 C、D、E 的伸缩值分别为:

C: ( 587 ÷ ( 1 + 1 + 1 ) ) × 1 ) = 195.67
D: ( 391.33 ÷ ( 1 + 1 ) ) × 1 = 195.665
E: ( 391 - 195.665 ÷ 1 ) × 1 = 195.335

总结来说,浏览器从剩余空间中减去已经分配的空间,然后计算下一个 flex 元素的伸缩值和最终大小。

差异化的 flex-grow

我们修改一下元素的 flex-grow 值:

div > :not([id=c]) {
flex: 1;
} [id=c] {
flex: 5;
}

此时,页面展示效果如下:

各元素的伸缩值计算如下:

A: 417px
B: ( 783 ÷ ( 1 + 1 + 5 + 1 ) ) × 1 = 98
C: ( 685 ÷ ( 5 + 1 + 1 ) ) × 5 = 490
D: ( 195 ÷ ( 1 + 1 ) ) × 1 = 97.5
E: ( 97.5 ÷ 1 ) × 1 = 97.5

设置 flex-basis: auto

前面的例子里,我们都设置了 flex-basis 的值为 0,现在我们设置 flex-basis: auto,再来看看元素尺寸是如何计算的。

删除其他元素的 flex 指定,修改样式如下:

[id=c] {
flex: 5;
}

此时等价于其他元素是 flex: 0 1 auto,元素 C 是 flex: 5 1 0。展示效果如下:

在前面的例子中,所有元素都是 flex-basis: 0,现在设置为 A、B、D、E 都设置成了 flex-basis: auto。因此,元素 A、B、D、E 都会使用元素内容的尺寸来计算。

因此,剩余剩余可分配空间大小为:

1200 - ( 417 + 33 + 35 + 30 ) = 685

因为元素 C 是唯一可伸缩的元素,因此它的伸缩值为 685。

设置 flex-shrink

现在我们做一些调整,让所有 flex 元素的尺寸总和大于容器的可用空间尺寸,使得 flex-shrink 生效。

样式调整如下:

:not([id=a]) {
flex-shrink: 1;
}
[id=a] {
flex-shrink: 5;
}
div > div {
flex-basis: 500px;
}

可以看到,所有元素 flex-grow: 0,元素 A flex-shrink: 5,其他元素 flex-shrink: 1,所有元素总尺寸 2500px。

显示效果如下:

因为元素尺寸总和超过了容器可用尺寸(1200px),因此 flex-shrink 将会生效。

元素 A 的伸缩值为:

( 1300 ÷  ( 5 + 1 + 1 + 1 + 1 ) ) × 5 = 722.22

则元素 A 的实际尺寸计算为:

500 - 722.22 = -222.22

可以看到得出的是负值。如果元素 A 是空元素,那么最终尺寸会是零。本例中,元素 A 的尺寸就是起内容的尺寸,大概 34px。

确定了元素 A 的尺寸之后,容器剩余空间尺寸为 1200 - 34 = 1166。其他元素的伸缩值计算为:

B: ( 1166 ÷ ( 1 + 1 + 1 + 1 ) ) × 1 = 291.5
C: ( 874.5 ÷ ( 1 + 1 + 1 ) ) × 1 = 291.5
D: ( 583 ÷ ( 1 + 1 ) ) × 1 = 291.5
E: ( 291.5 ÷ 1 ) × 1 = 291.5

值得注意的一点是,如果 flex-growflex-shrink 的取值都为 0,那么元素即不会放大也不会缩小,当所有元素的尺寸总和超过容器空间之后,就会产生溢出效果。

设置 flex-wrap

当所有元素的尺寸总和超过容器空间之后,就会产生溢出效果。我们可以通过设置 flex-wrap: wrap 来是超出的元素换行。

我们调整下样式:

div > div {
flex-basis: 500px;
}
[id=flex] {
flex-wrap: wrap;
}

显示效果如下:

我们可以看到,由于 flex-basis: 500px 且默认的 flex-grow: 0,因此每一行的末尾有 200px 的剩余空间。

我们可以通过设置 flex-grow: 1 来让元素占满剩余空间。

div > div {
flex: 1 1 500px;
}

此时显示效果如下:

可以看到,每行的剩余空间都被占满了。

总结

Flexbox 布局有时候会有一些复杂和难以理解。在实际使用过程中,我们需要牢记如下几点:

  • Flexbox 只在单一方向上分配空间,行或者列。
  • flex-basis 定义了元素的最小尺寸,有时候元素的内容尺寸可能会比 flex-basis 定义的尺寸大。
  • 浏览器同时只会使用 flex-grow 或者 flex-shrink 来排列元素,不会同时使用。
  • 实际使用 flex-grow 还是 flex-shrink 取决于元素尺寸总和与容器剩余空间的大小。
  • 浏览器根据 flex 因子以及元素本身设置的系数来分配每个元素的空间。

关注微信公众号,获取最新推送~

加微信,深入交流~

原来 flexbox 是这么工作的的更多相关文章

  1. 使用顶级 VSCode 扩展来加快开发 JavaScript

    使用顶级 VSCode 扩展来加快开发 JavaScript 发表于 2018年08月24日 by 愚人码头 被浏览 3,942 次 分享到:   小编推荐:掘金是一个面向程序员的高质量技术社区,从 ...

  2. Flexbox 自由的布局

    css3提出了一种新的布局方式.她并没有以摧枯拉朽之势博得我的喜爱.我和她的故事总是伴随着苦涩的味道.世道变了,总要做出些选择才能跟紧步伐.她很强大,能满足你天马行空的需求而不必抓掉一大把头发.她却很 ...

  3. [译]flexbox全揭秘

    原文:http://css-tricks.com/snippets/css/a-guide-to-flexbox/ 弹性布局(弹性盒子,现今仍是w3c的候选推荐),目标在于,对于一个容器中的各个项目块 ...

  4. 前端工作面试问题--摘取自github

    前端工作面试问题 本文包含了一些用于考查候选者的前端面试问题.不建议对单个候选者问及每个问题 (那需要好几个小时).只要从列表里挑选一些,就能帮助你考查候选者是否具备所需要的技能. 备注: 这些问题中 ...

  5. flexbox-CSS3弹性盒模型flexbox完整版教程

    原文链接:http://caibaojian.com/flexbox-guide.html flexbox-CSS3弹性盒模型flexbox完整版教程 A-A+ 前端博客•2014-05-08•前端开 ...

  6. 转载:CSS3 Flexbox可视化指南

    0. 目录 目录 引言 正文 1 引入 2 基础 3 使用 4 弹性容器Flex container属性 41 flex-direction 42 flex-wrap 43 flex-flow 44 ...

  7. 【转】Flexbox——快速布局神器

    原文转自:http://www.w3cplus.com/css3/flexbox-basics.html 简介 在很多方面HTML和CSS是一个强大的内容发布机制——易学.灵活和强大.但复杂的布局是他 ...

  8. CSS3弹性盒模型flexbox完整版教程

    http://caibaojian.com/flexbox-guide.html 来自CSS Tricks上的一个教程,原文为:A Complete Guide to Flexbox.文中详细的介绍了 ...

  9. Flexbox——快速布局神器

    Flexbox通常能让我们更好的操作他的子元素布局,例如: 如果元素容器没有足够的空间,我们无需计算每个元素的宽度,就可以设置他们在同一行: 可以快速让他们布局在一列: 可以方便让他们对齐容器的左.右 ...

随机推荐

  1. QT之HTTP

    概述 QT的HTTP操作都是异步的,内部通过线程实现. 相关类: QNetworkAccessManager [发送网络请求并接收响应] QNetworkReply [服务响应] QNetworkRe ...

  2. js 数组/对象/日期的浅克隆

    //封装 function clone (obj) { // Handle the 3 simple types, and null or undefined if (null == obj || & ...

  3. php 修改后端代码参考

    后端代码参考:

  4. 当.Net撞上BI可视化,这3种“套路”你必须知道

    最近葡萄在做技术支持,又遇到了客户给我们出的新问题. 事情是这样的. 这次客户使用的是.Net项目,直接做BI大屏过于复杂,所以想直接集成使用BI数据可视化分析大屏. 所以,这次我们就从--Wyn出发 ...

  5. 8、msyql性能分析工具

    性能分析工具 1服务器优化的步骤 2查询系统参数 在MySQL中,可以使用 SHOW STATUS 语句查询一些MySQL数据库服务器的性能参数.执行频率 . SHOW STATUS语句语法如下: S ...

  6. yum 安装的历史与撤销(yum history undo )

    我们可以使用yum history 来查看yum的历史记录 想撤销安装则输入 yum history undo <ID号> 即可撤销yum的安装/升级

  7. Linux卸载源码编译安装的软件

    使用auto-apt 和 checkinstall,具体命令如下 #安装auto-apt和checkinstall apt install auto-apt checkinstall #在源码目录中 ...

  8. 基于python 实现冒泡排序算法

    #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/11/17 14:42 # @Author : gylhaut # @Site ...

  9. C#: .net序列化及反序列化 [XmlElement(“节点名称”)] [XmlAttribute(“节点属性”)] (上篇)

    .net序列化及反序列化 序列化是指一个对象的实例可以被保存,保存成一个二进制串,当然,一旦被保存成二进制串,那么也可以保存成文本串了.比如,一个计数器,数值为2,我们可以用字符串"2&qu ...

  10. C# WinForm 解决子窗体放大后,子窗体图标放大的问题

    解决子窗体放大后,子窗体的图标占用主窗体的菜单栏的问题. C#子窗体最大化时, 那个图标跑到主窗体的前面去了, 造成界面不统一也不美观. 所以需要进行处理, 只要有主窗体的菜单ItemAdded事件中 ...