Flex布局详解

267 阅读10分钟

本篇没有流水账,关于基础知识的学习请移步MdnWebDoc

Flex为什么存在

flex布局出现之前,布局的实现方式往往通过float浮动和position定位的方式实现,因此出现了许多问题:

  1. 浮动脱离文档流导致的重叠、清除浮动、高度坍塌问题:简单的布局却需要非常多的浮动清除!还要提防子元素都浮动导致父元素坍塌,这太让人头大了!
  2. 垂直水平居中,特别是垂直居中,实现起来并不容易!我仅仅是想垂直居中而已!?需要这么复杂?
  3. 行内样式的垂直边距不可用且高度和宽度不能指定,外边距折叠问题。。。

以上种种导致简单的布局却需要大量的代码实现,而flex布局则是破局者;

值得一提的是:虽然W3C官网对于CSS Flexible Box Layout Module Level 1的规范始终没有转换为Recommendation正式标准,而是一直处于Candidate Recommendation候选标准,但是Flex已经被各大主流浏览器广泛支持

Flex容器本身还是块级元素吗?

<div style="display: flex"></div>

此处需要注意:flex布局只是影响其内部元素的布局,对于与容器同级的元素,其表现和普通的块级元素没有区别

同时有:

<div style="display: inline-flex"></div>

可以让容器对同级元素遵守行内元素的规则,但是与纯正的inline不同的是,其高度和宽度以及垂直边距都会生效

然而这并不代表直接指定display:flex会让容器抛弃块级元素的特性,例如此时容器默认仍占满父元素,垂直方向仍会发生外边距折叠。

一维布局以及为什么在Flex之外还需要有Grid

flex一维布局,这代表其有主轴的概念,这也就决定了其在交叉轴上的其他行只能靠主轴上的元素溢出得到,这导致了其在部分场景下的局限性

如:

<div class="div-container">
    <div class="div-child">123</div>
    <div class="div-child">123</div>
    <div class="div-child">123</div>
    <div class="div-child">123</div>
    <div class="div-child">123</div>
    <div class="div-child">123</div>
    <div class="div-child">123</div>
</div>
<style>
    .div-container {
        width: 700px;
        height: 300px;
        border: 1px solid red;
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-start;
    }
    .div-child {
        box-sizing: border-box;
        width: 200px;
        height: 100px;
        border: 1px solid green;
    }
</style>

image.png

如果我想让最后一个div不在最前方,而是最后面,只能通过margin-left:auto边距的形式,这还好说,可是如果最后一行有两个元素,我想让他们像space-around一样分布呢?好的办法是将他们分别放在多个div,可是这增加了html结构的复杂性,因此grid布局就有存在的意义。

简单点理解,其实就是flex没有justify-self,至于为什么没有,请见flex存在网格?

从Grid和Flex通用属性看网格和元素在容器中的分布

一般指以下几个属性

  1. justify-content
  2. justify-items
  3. justify-self
  4. align-items
  5. align-self
  6. align-content

你也许注意到了:好像gridflex有些属性是通用的?

justify-contentjustify-items好像很像?为什么需要有两个差不多的属性存在?

对于以上问题请见flex存在网格?

Flex布局中的元素还是普通的块级元素吗?

尺寸

当未指定flex-basiswidth属性时,flex中元素默认的宽度是其内容的宽度

当未指定元素height时,align-items:sketch时,且未发生换行,元素高度为容器高度

当未指定元素height时,align-items:sketch时,且发生换行,元素高度为其网格高度

当取其他值时,元素高度为其内容高度

可见尺寸不遵循块级元素撑满父元素的特性

为什么flex布局中会有网格?请见flex存在网格?

像是行内元素?还是块级元素?

flex布局中的元素默认表现得像是不会换行的块级元素,且不会发生外边距折叠现象

因此其既不是行内元素,也不是块级元素,行内元素和块级元素只是常规流布局中的特殊表现形式,并不适用于flex

flex属性

是一个容易引起歧义的属性

flex-grow,flex-shrink,flex-basis的简写形式

决定了元素的固定尺寸,拓展和缩小规则,以及元素实际占据的空间

flex-basis

本意是指定元素的初始尺寸

有以下特点

  1. 默认值是auto,如果指定了width,则使用width,没有指定width则使用内容宽度
  2. 该值为content时,会元素内容的宽度,即使已经设置了宽度。
  3. 指定该值时,其优先级高于width
  4. 默认值为auto
  5. 该值并不是决定的元素实际宽度,而是固定宽度,元素的实际宽度应该加上根据grow,shrink分配的部分

flex-grow

flex-grow接收比例值,这个属性的意义为"当众子元素元素的固定宽度不足以撑满父元素宽度时,按照什么样的比例分配剩余空间",当为0时,不会分配剩余空间

固定宽度指的是flex-basis的取值

分配算法:

  1. 确定父容器的主轴空间和子元素的 flex-basis
  2. 计算剩余空间:用父容器的主轴空间减去所有子元素的 flex-basis 总和(以及外边距、边框和内边距等所占空间),得到剩余空间
  3. 计算flex-grow总和
  4. 按比例分配剩余空间:对于每个设置了 flex-grow 值大于 0 的子元素,按照其 flex-grow 值占 flex-grow 总和的比例来分配剩余空间。计算公式为: 子元素分配到的额外空间 = 剩余空间 × (子元素的 flex-grow 值 / flex-grow 总和)

子元素最终的主轴content大小:子元素的 flex-basis + 子元素分配到的额外空间

子元素最终的主轴占据空间 = 子元素的 flex-basis + 子元素分配到的额外空间+padding+margin+border

需要注意的是,元素的content指的是元素除了padding,border,margin的实际内容区域;box-sizing决定的是width是否包含borderpaddingbackground-clip决定的是背景区域是否延伸到padding,border还是只在content又或者是只存在于text中。todo:新开一篇说一下这块。

flex-shrink

flex-shrink接受比例值,代表众子元素宽度超出父元素宽度时,如何缩小子元素

有趣的是,flex-shrinkflex-grow行为并不不相同,flex-shrink表示缩小因子,而并非flex-grow的比例。

计算算法:

  1. 确定父容器的主轴空间和子元素的 flex-basis
  2. 计算溢出空间:用所有子元素的 flex-basis 总和(包含外边距、边框和内边距等所占空间)减去父容器的主轴空间,得到溢出空间。
  3. 计算加权收缩因子总和:公式为:加权收缩因子 = 子元素的 flex-shrink 值 × 子元素的 flex-basis(或内容宽度)。然后将所有子元素的加权收缩因子相加,得到加权收缩因子总和。
  4. 按比例收缩元素:对于每个设置了 flex-shrink 值大于 0 的子元素,按照其加权收缩因子占加权收缩因子总和的比例来收缩空间。计算公式为: 子元素收缩的空间 = 溢出空间 × (子元素的加权收缩因子 / 加权收缩因子总和)

子元素最终的主轴content大小 = 子元素的 flex-basis - 子元素收缩的空间

子元素最终的主轴占据空间 = 子元素的 flex-basis - 子元素收缩的空间+padding+margin+border

我们会发现,不同于flex-grow相同的值会分配相同的额外空间,flex-shrink更倾向于对于基础宽度大的元素更多的缩小,这其实和我们的直觉的相同的:如果小元素和大元素缩小的空间相同,那么小元素很快就消失不见,而此时大元素才刚刚缩小了一点!

flex-shrink更倾向于给出一个合理且符合直觉的结果!

注意:border会随着屏幕缩放改变其值!而margin,padding,width则不会,尽管其在屏幕上实际占据的屏幕像素发生了改变,其在devtools中的值也不会发生改变。todo:屏幕的实际像素和devtools中的像素有什么关系?

两者默认值以及其导致的一些奇怪的现象

flex-grow默认值是0,这就代表如果主轴有剩余空间,其不会自动拓展

flex-shrink默认值是1,这代表如果元素溢出,默认情况下会收缩以符合容器宽度

因为flex-shrink的默认值是1,因此需要注意有时候明确需要溢出的情况:

<div class="container">
    <div class="child"></div>
    <div class="child"></div>
</div>
<style>
    .container {
        width: 300px;
        height: 300px;
        border:1px solid red;
        display: flex;
        flex-direction: column;
        overflow: auto
    }
    .child {
        height: 200px;
        width: 100px;
        border: 1px solid blue;
    }
</style>

此处子元素明显高度大于容器,且overflow: auto表示代码意愿是希望溢出的部分通过滚动条调节,实际效果如下:

image.png

由于flex-shrink会对溢出的部分进行收缩,因此子元素不会溢出,如需实现效果,需要为每个子元素加上flex-shrink:0;

flex:1

flex: 1 1 0的缩写,也就是flex-basis:0;flex-grow:1;flex-shrink: 1;,其中有几点需要注意

  1. flex-basis已经被指定,因此width会被忽略
  2. flex-shrink不为0,子元素不会溢出容器
  3. 但是如果其内容超过了分配的宽度,则以内容宽度为准,直至内容宽度超出容器宽度

问题:对于多个flex:1的子元素,其宽度一定相同吗?

答案是:不一定,尽管flex-basis:1会忽略元素的width,以下几种情况都会导致其宽度不同

  1. max-widthmin-width的影响下,以该值为准
  2. 存在padding,margin,border的影响下,flex-grow只是保证了其content相同,但是实际占据的空间不会相同
  3. 元素的内容宽度大于分配宽度时候,宽度以内容宽度为准,直至达到容器宽度
  4. 当元素发生换行的时候,新一行的元素会重新计算其宽度

对于以上的第四点:

<div class="container">
    <div class="child" ></div>
    <div class="child"></div>
    <div class="child"></div>
</div>
<style>
    .container {
        width: 100px;
        height: 105px;
        border:1px solid red;
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
    }
    .child {
        height: 50px;
        width: 100px;
        border: 1px solid blue;
        flex:1;
        min-width: 40px;
    }
</style>

image.png

换行后的元素会在新一行重新计算其宽度

没办法调节主轴的单个元素的分布吗?

flex布局并没有类似justify-self的属性,因此无法在flex官方属性层级调节

具体原因见flex存在网格?

但是可以通过margin:auto,让外边距自动计算来达到差不多的效果。

如果想要在官方属性层级进行调节,请使用 Grid布局

仍在完善中ing~~~