阅读 358

Flex Layout 详解

2019/5/7号二次更新:为折叠的flex item的例子增添了codePen链接。
2019/5/7号更新:补充了整体结构,如有疑问,欢迎留言。


总想写flex,却一直拖——拖——拖,今天算是写完了。与以往的文章不同,这篇文章更像是写给自己看的,所以有些术语不会再解释,如看不懂,可以先去这篇文章里看看术语.

整体结构

我们先看这个例子

div.parent {
display: flex;
width: 1000px;
height:400px;
border: solid 2px rgba(75, 234, 12, 0.7);
border-radius: 8px;
}
div.parent > div {
border-radius: 8px;
flex: 1 1 auto;
color: #fff;
text-align: center;
line-height: 200px;
font-size: 26px;
}
div.first-child {
width: 200px;
height: 200px;
background-color: rgb(64, 166, 249);
}
div.second-child {
width: 400px;
height: 200px;
background-color: rgb(255, 141, 75);
}
复制代码
<div class="parent">
<div class="first-child">first-child</div>
<div class="second-child">second-child</div>
</div>
复制代码

效果如下:

上面的父级box是个flex container, 有两个flex item(div.first-child和div.second-child)。

flex direction默认为row,那么横坐标轴称作main axis,flex container(flex item)在main axis方向的尺寸(即宽度)称作flex container(flex item)的main size, flex container的main size方向的起点叫main start,终点叫main end。

纵坐标轴称作cross axis,,flex container(flex item)在cross axis方向的尺寸(即高度)称作flex container(flex item)的cross size。flex container的cross size方向的起点叫cross start, 终点叫cross end。

在flex container里,flex item 会沿着main start依次排列,直至排到main end。接着换行,从cross satrt 开始往cross end方向排列。

整体结构就大致如上,接下来会逐一讲解flex container,flex item,以及它们之间的渲染原理。

Flex Container

  1. display为flex的元素会产生一个flex container,该container在正常流内以block-level的形式存在。

  2. display为inline-flex的元素会产生一个flex container,该container在正常流内以inline-level的形式存在。

  3. 每个flex container都会为它的内容建立一个flex formatting context,这与block container为它的内容建立一个block formatting context很类似。但是,flex container并不是block container, 诸如:

  • float和clear在flex container里面无效(也就是flex item即使设置了float和clear也不起作用)

  • vertical-align在flex item上也不起作用

  • flex container的margin不会与它的内容的margin重叠

  • ::first-line和::first-letter伪元素在flex container上不起作用


注意:当元素为绝对定位元素、浮动元素或根元素时,display属性值的computed value的计算规则参照下表
Specified value Computed value
inline-table table
inline, table-row-group, table-column, table-column-group, table-header-group, table-footer-group, table-row, table-cell, table-caption, inline-block block
inline-flex flex
others same as specified

Flex Item

简单的说,flex container的每个孩子都是一个flex item,而每个flex item都是一个flex-level box,另外,由于在计算computed value时flex item会发生块级化,因此每个flex item也是一个block container。而对于没有html元素包裹的连续文字,会被自动包裹在一个匿名的block container flex item里。

释1: 块级化 —— 尽管有的flex item的display属性值为inline,但是在计算computed value时,也会被设为block.

释2: block container ——该box只能包含block-level box或是建立一个inline formatting context且只能包含inline-level box

1. flex item为绝对定位元素

因为为绝对定位元素,所以该flex item会脱离流。效果就如同该flex item的static position box(在计算绝对定位元素位置时,会先假设它的position为static,处于正常流中,然后得出一个static position,再依据这个static position去定位。)为flex container的content box。也就是该flex container里面只有该flex item的static position box,这个flex container是个匿名的flex container。

例:为div.first-child加上绝对定位。div.first-child改为:

div.first-child {
Width: 200px;
height: 200px;
background-color: rgb(64, 166, 249);
position: absolute;
}
复制代码

效果如下:

选中div.second-child可以看到,它的宽度独占1000px。

如果给绝对定位flex item加上align-self: center,它会处于它所在的flex container的交叉轴的中心位置。修改div.first-child如下:

div.first-child {
width: 200px;
height: 200px;
background-color: rgb(64, 166, 249);
position: absolute;
align-self: center;
}
复制代码

效果如下:

而一旦为绝对定位flex item设置了top/bottom,它就会参照第一个父级定位元素去移动,align-self也就失效了。现在设置div.parent为相对定位元素,修改成:

div.parent {
display: flex;
Width: 1000px;
height:400px;
border: solid 2px rgba(75, 234, 12, 0.7);
border-radius: 8px;
position:relative;
}
复制代码

为div.first-child设置偏移量,修改如下:

div.first-child {
width: 200px;
height: 200px;
background-color: rgb(64, 166, 249);
position: absolute;
align-self: center;
left: 50px;
top: 50px;
}
复制代码

效果如下:

对比上图,可以看到交叉轴上div.first-child距离div.parent的上边缘只有上图的一半,也就是50px,同时div.first-child距离div.parent的左边缘也是50px。

2. flex item的margin和padding

  • 相邻的flex item的margin不会重叠(flex container的margin也不会与flex item的margin重叠)。
  • margin和padding若设置为百分比,则百分比是基于flex container的inline size(若writing mode是horizontal,则inline size为宽度;若mode是vertical,则inline size 为高度 )。

例子:
div.parent {
display: flex;
flex-wrap: wrap;
width: 400px;
height: auto;
background-color: rgba(15, 241, 170, 0.42);
border-radius: 8px;
margin-top: 50px;
}
div.parent > div {
border-radius: 8px;
flex: 1 1 auto;
color: #fff;
text-align: center;
line-height: 200px;
font-size: 26px;
}
div.first-child {
width: 200px;
height: 200px;
background-color: rgb(64, 166, 249);
margin-top: 50px;
margin-bottom: 50px;
}
div.second-child {
width: 400px;
height: 200px;
background-color: rgb(255, 141, 75);
margin-top: 50px;
}
复制代码

效果如下:

可以看到div.parent距离root box的上边缘有50px,而div.first-child的上边缘距离div.parent的上边缘也有50px,他们并没有重叠。div.first-child及div.second-child的margin-top也没有重叠。

如果div.parent的display为block,并且div.parent及div.first-child的border宽度均为0,它和div.first-child的margin-top会发生重叠。修改div.parent如下:

div.parent {
display: block;
width: 400px;
height: auto;
background-color: rgba(15, 241, 170, 0.42);
border-radius: 8px;
margin-top: 50px;
}
复制代码

效果如下:

鼠标定位到div.parent上,可以看到它的margin-top和div.first-child的margin-top确实重叠了。同理,div.first-child与div.second-child的margin-top也重叠了。

3. 折叠的flex item

  1. Flex item可以设置visibility: collapse,达到折叠的效果。效果类似于table的行折叠。
  2. Flex item即使折叠了,但依然有个隐形的空壳存在,为的是保证flex container的cross size的稳定。如果flex container仅有一个flex line(每条flex line上按main size方向依次摆放flex item,类似于line box),那么某个flex item折叠可能会影响flex container 的main size,但是不会影响flex container的cross size。
  3. 尽管折叠的flex item不会被渲染,但依然会出现在dom 树上。为了计算折叠的flex item的那个空壳的大小,在dom树形成的过程中,flex layout 会先假设所有item都没有折叠,然后在由dom树形成渲染树的过程中,会将折叠的flex item用一个保留了原始的cross size的空壳替换掉。

例子:

div.collapse-example {
  display: flex;
  > ul.nav {
    height: auto;
    margin: 0;
    margin-right: 20px;
    > li {
      margin-bottom: 5px;
      border-radius: 4px;
      text-align: center;
      font-size: 18px;
      display: flex;
      flex-flow: column;
    }
    > li:target,
    > li:hover {
      cursor: pointer;
      > a {
        border-top-left-radius: 8px;
        border-top-right-radius: 8px;
        font-weight: bold;
      }
      > ul {
        border-radius: 8px;
      }
      a {
        color: #343434 !important;
      }
    }
    > li:not(:target):not(:hover) > ul {
      height: 0;
      overflow: hidden;
      visibility: collapse;
    }
  }
  ul {
    padding: 0 10px;
    > li {
      list-style: none;
    }
  }
}
复制代码
<div class="collapse-example">
        <ul class="nav">
          <li>
            <a href="#">About</a>
            <ul>
              <li>
                <a href="#">introduction</a>
              </li>
              <li>
                <a href="#">blog</a>
              </li>
            </ul>
          </li>
          <li>
            <a href="#">Product</a>
            <ul>
              <li>
                <a href="#">book</a>
              </li>
              <li>
                <a href="#">beautiful-closes</a>
              </li>
            </ul>
          </li>
          <li>
            <a  href="#">Contact</a>
            <ul>
              <li>
                <a href="#">email</a>
              </li>
              <li>
                <a href="#">weixin</a>
              </li>
            </ul>
          </li>
        </ul>
        <article id="main">
            This is an Example 
        </article>
      </div>
复制代码

效果如下:

鼠标移到菜单上,子菜单会显示出来。(点击链接看看

4. flex item的automatic minimum size

min-width和min-height有了一个新的初始化值,即auto,表示自动设置的min size。

对于flex item,当min-width或min-height的specified value设置为auto,相应的used value是基于内容来设置的。这个值怎么定呢?

对于min-width,一般,取主轴上width的specified value与主轴上min-content size(一个box的最小size,该size不会导致overflow)的较小值; 如果主轴上width没有specified value, 但是有个ratio且交叉轴上width设置了specified value,则根据ratio及交叉轴width的specified value得出一个转换后的size,再取这个size和主轴上min-content size的较小值;如果既没有设定主轴width的specified value有没有ratio,那么直接取主轴上min-content size。

同理min-height。

通常,automatic minimum size取值合理,但是如果是以下情况,还是为min size设置一个具体的值比较好。

  1. 当flex item的content是一篇较大的文档,给min-width或min-height设置成字体相关的值比较好,例如min-width: 12em。使用 automatic minimum size可能会导致溢出。
  2. 当flex item有较多的孩子节点时,automatic minimum size的取值需要基于min-content size, 而 layout engine为了找到min-content size需要一一遍历所有的孩子节点,有损性能。如果直接给min size设置specified value,就不必去找min-content size了。
注意:ratio表示一个元素的width与height的比例关系,一般通过给padding-top设定百分比来表达一个固定比例。(因为padding的百分比值是参考containing box的宽度的)
aspect ratio padding-top value
1:1 100%
16:9 56.25%
4:3 75%
3:2 66.66%
8:5 62.5%

定向和排序

flex container的内容可以按照一定的方向和顺序排列。主要依靠flex container的flex-direction和flex-wrap以及flex item的order。

但是有点需切记,不可依靠flex-direction/flex-wrap 的-reverse值 以及order来代替flex item的原本顺序,会影响页面的可访问性。

1. flex-direction

Name: flex-direction
Value: row | row-reverse | column | column-reverse
Initial: row
Applies to: flex containers
Inherited: no
Percentages: n/a

2. flex-wrap

Name: flex-wrap
Value: nowrap | wrap | wrap-reverse
Initial: nowrap
Applies to: flex containers
Inherited: no
Percentages: n/a

flex: nowrap表示flex container为单行,内容大小超出则调整flex item的大小以免溢出。

flex: wrap 表示在内容过多时flex container会换行

flex: wrap-reverse 表示在内容过多时flex container会换行,但cross-start及cross-end方向调换。

3. Order

Name: order
Value:
Initial: 0
Applies to: flex items
Inherited: no
Percentages: n/a

order可以设置flex item的位置。 这些flex item会在文档顺序的基础上再基于order属性值进行再排序,然后按照再排序的结果渲染出来。如果某些flex item的order值相同,则按照它们在文档里的顺序渲染。

例子:
div.parent {
display: flex;
width: 400px;
height: auto;
border: solid 2px rgba(15, 241, 170, 0.42);
border-radius: 8px;
}
div.parent > div {
border-radius: 8px;
flex: 1 1 auto;
color: #fff;
text-align: center;
line-height: 200px;
font-size: 26px;
}
div.first-child {
width: 200px;
height: 200px;
background-color: rgb(64, 166, 249);
}
div.second-child {
width: 400px;
height: 200px;
background-color: rgb(255, 141, 75);
order: -1;
}
复制代码

效果如下:

灵活性

1. flex-grow

当一个flex line还有多余的空间可扩展,这些空间如何分配给该行的flex item呢?用flex-grow, 它用来设置某个flex item可扩展多少比例的多余空间。取值为number,默认为0。

2. flex-shrink

当一个flex line的空间不足,这些缺失的空间如何分担给该行的flex item呢?用flex-shrink,它表示某个flex item需要缩小多少比例的空间,取值为number,默认为1。

3. flex-basic

定义了分配多余空间(可为正数亦可为负数)前该flex item所占的main size,浏览器会根据该值来计算主轴上的多余空间。可设定如下属性值:

  • auto: 当specified value为auto时,按如下步骤取值。 第一步:采用的used value为flex item的main size(即主轴上的width或height的属性值), 第二步:如果这个used value依然为auto,那么used value 会基于 flex item的content 得到一个具体值。
  • content: 基于flex item的content得到一个automatic size。
  • <width>: 和width或height的设置方式一样,可设为<length> 或 <percentage>

默认值为auto。当主轴为水平方向,设置了flex-basic,则flex item的width值会失效。例如,如果某个flex item的flex-basic设为0,则把该flex item的宽度视为0,即使它本身width为100px,这个100px也会被纳入多余空间中,供flex inline的所有flex item一起分配。

4. flex

flex属性为flex-grow flex-shrink flex-basic的缩写,默认值为0 1 auto,可设定的值为:

  • initial: 0 1 auto,当有多余空间时,没有弹性;当空间不足时,flex item可以缩小。
  • auto: 1 1 auto,flex会有充足的弹性
  • none: 0 0 auto, flex会完全没有弹性
  • <positive-number>: <positive-number> 1 auto
  • <flex-grow> <flex-shrink> <flex-basic>: 若flex-grow省略了,默认值取1;若flex-shrink省略了,默认值取1;若flex-basic省略了,默认值取0。

通常,flex item不会缩小到比min content sizes还要小。为防万一,我们可以为flex item设置min-width或min-height。


对齐

1. 使用auto margin对齐

  • 如果设置了flex-grow,auto margin 会被设置为0;若设置了flex-basis,没有设置flex-grow,那么在主轴方向上,根据flex-basis计算出的多余的空间都会分配给auto margin
  • auto margin的优先级高于justify-content及align-self,轴上任何多余的空间(不包括负的空间)都会分配给auto margin。
  • 如果box在某个轴上发生溢出了,则auto margin会被忽略,且box在该轴的尾部溢出。(如果既有auto margin, 又有justify-content/align-self/align-items,依然是忽略justify-content/align-self/align-items,采用auto margin,只不过此时因为overflow,接着会忽略auto margin, 在轴的尾部溢出)

总的来说,当存在auto margin时,又有多余的空间(不包括负的空间),则优先级如下:

flex-grow > auto margin > justify-content/align-self/align-items

如果box在轴上只剩下负的空间(即溢出),则auto margin被忽略。

例子:
div.parent {
display: flex;
flex-wrap: nowrap;
justify-content: center;
width: 1000px;
height: auto;
border: solid 5px rgba(15, 241, 170, 0.42);
border-radius: 8px;
}
div.parent > div {
border-radius: 8px;
flex: 1 1 auto;
color: #fff;
text-align: center;
line-height: 200px;
font-size: 26px;
}
div.first-child {
width: 200px;
height: 200px;
background-color: rgb(64, 166, 249);
margin-left: auto;
}
div.second-child {
width: 400px;
height: 200px;
background-color: rgb(255, 141, 75);
}
复制代码

现在div.parent的justify-content设为center, div.first-child及div.second-child设置了flex: 1 1 auto(其中flex-grow: 1, flex-shrink: 1, flex-basis: auto); 同时div.first-child设置了margin-left为auto,效果如下:

可以看到div.first-child及div.second-child均扩展了,justify-content及auto margin没起作用。

现在设置div.first-child及div.second-child的flex为0 1 auto,修改如下:

div.parent > div {
border-radius: 8px;
flex: 0 1 auto;
color: #fff;
text-align: center;
line-height: 200px;
font-size: 26px;
}
复制代码

效果如下:

可以看到,剩余空间都被div.first-child的auto margin-left 占据了。

现在去掉div.first的auto margin,修改如下:

div.first-child {
width: 200px;
height: 200px;
background-color: rgb(64, 166, 249);
}
复制代码

效果如下:

现在,justify-content起作用了。

2.justify-content对齐

justify-content用来设置flex-item在主轴上的对齐方式。

Name: justify-content
Value: flex-start | flex-end | center | space-between | space-around
Initial: flex-start
Applies to: flex containers
Inherited: no

效果如下:

3. align-items和align-self

align-items定义了flex container中flex-item在交叉轴上的对齐方式,有点类似justify-content,是针对所有flex item。

align-self定义了某个flex item在交叉轴上的对齐方式,会覆盖align-items的值。

Name: align-items
Value: flex-start | flex-end | center | baseline | stretch
Initial: stretch
Applies to: flex containers
Inherited: no

Name: align-self
Value: auto | flex-start | flex-end | center | baseline | stretch
Initial: auto
Applies to: flex items
Inherited: no

  • auto: 对于flex-item,可以设置align-self为auto, auto值的computed value会设置为flex container的align-items的值。
  • center: flex item的margin box 在交叉轴方向上处于flex line的中间位置。如果flex line的高度比flex item的还要低,则flex item在交叉轴的首尾两端溢出相等部分。
  • baseline: flex item会依照flex line的baseline对齐。在交叉轴方向上,baseline与flex item的margin box的上边缘距离最远的那个flex item会被放置在交叉轴的开始位置。
  • stretch: 若flex item的 cross size的computed value为auto(如果flex-direction为row,那么cross size就是flex item的height),且交叉轴方向的margin均不为auto,那么align-items/align-self为stretch时,flex item会被拉伸。flex item的cross size的used value会尽可能的接近flex line的高度,如果flex item设置了min(max)-width(height),那么这个used value会受它们的限制。
效果如下图:

4. align-contents

Name: align-content
Value: flex-start | flex-end | center | space-between | space-around | stretch
Initial: stretch
Applies to: multi-line flex containers
Inherited: no

align-content定义了flex line在flex container的交叉轴方向上的对齐方式,类似于justify-content,都属于flex container属性。只不过它要对齐的对象是flex line,且在交叉轴方向,而justify-content要对齐的对象是flex item,在主轴上。

属性值如下:

space-around: flex container中的flex line之间的空间相等,第一个flex line与 flex container的margin box的上边缘之间的空间只有flex line之间的空间的一半。

效果如下:

注明:本篇文章中少数图片来源网络,如有侵权,请立即联系本人删除。

关注下面的标签,发现更多相似文章
评论