CSS – Flex

前言

Flex 诞生在 Float 之后, Grid 之前, 它主要是取代 Float 来实现布局. 而它没有 cover 到的地方则由 Grid 弥补.

所以当前, 我们做布局时, 几乎不用 Float, 大部分都用 Flex, 少许地方用 Grid (通常 Flex 搞不定才会用 Grid).

BTW, Figma 的 Auto Layout 就是用 Flex 实现的.

 

参考

Youtube – Flexbox is more complicated than you thought

Youtube – Learn Flexbox in 15 Minutes

Youtube – Learn flexbox the easy way

 

Flex 的能力

抽象的说, Flex 的能力是把几个 element (block 也好, inline 也要) 排成一行或一列, 然后做一些排版, 比如 alignment, spacing, dimension 等等.

然后它还带有一些 RWD 的能力.

 

Flex 常用地方

参考: Youtube – Flexbox design patterns you can use in your projects (读完本篇后可以看看)

Alignment

本来 2 个 element 是一列的, 通过 flex 变成了一行, 而且 vertical aligh center.

Spacing

不使用 Flex 的话, 一般上我们用 margin-top 来做 spacing, 但 margin-top 的表达不理想, 比如第一个 element 不需要 margin-top. 所有的 margin-top 都是相同的值, 所以用 Flex 会更好表达.

RWD

Flex 还可以用在这类简单的 RWD 上. 

 

Flex 的 HTML 结构

<div class="container">
  <div class="item">item1</div>
  <div class="item">item2</div>
  <div class="item">item3</div>
  <div class="item">item4</div>
</div>

Flex 有 2 层, container > item

只有 container 内的 first layer element 才是 flex item.

我们给点 style 看看它的长相

.container {
  width: 250px;
  border: 1px solid red;

  > * {
    &:nth-child(odd) {
      background-color: pink;
    }
    &:nth-child(even) {
      background-color: cyan;
    }
  }
}

此时还没有加入 flex, 效果:

由于 div 默认是 display block, 所以 item 1,2,3,4 会往下发展形成 1 列.

 

Flex 的默认值

现在我们加入 display: flex 看看它的效果

display: flex;

虽然只给了一个属性, 但是 Flex 有许多默认值. 所以它其实是这样的

.container {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  justify-content: flex-start;
  align-items: stretch;
  align-content: stretch;
  gap: 0;

  > * {
    flex-grow: 0;
    flex-shrink: 1;
    flex-basis: auto;
  }
}

效果

和原先的已经完全不一样了.

 

Flex 属性 (过一轮)

先过一轮每一个属性, 还有它大致上的作用. 有个画面先, 后面才逐一细品.

flex-direction (container)

flex item 的方向, row 表示 horizontal 横向, column 表示 vertical 垂直

当 direction: row 时, item 即使是 display: block 也会被强行排成一行, 看上去就像是变成了 inline.

flex-wrap (container)

flex-wrap 表示当 item 超过 container width 时, 应该如何摆放

wrap 表示把 item 放到下一行

nowrap 则表示让 item 超出 container width

默认是 nowrap, wrap 通常用来做 RWD.

justify-content (container)

justify 是做 alignment 的, align 的 direction 和 flex-direction 一致. 比如上面的例子 flex-direction 是 row, 所以 align 的 direction 就是 horizontal.

align 的前提是要有额外的空间, 比如上面这个例子, container width > items total width 所以有空间可以 align.

align-items (container)

align-items 也是做 align 的, align 的 direction 和 flex-direction 相反. 比如上面的例子 flex-direction 是 row, 那它 align 的 direction 就是 vertical.

有 2 种情况可以 align.

第一, container height > item height 有额外的空间, 像上面这样.

第二, item 的高度不一致时. 总之要 align 得要有空间.

另外, 它有一个特别的属性叫 stretch 拉紧, 意思是 item height fill container

注: container 的 align-items 是可以被 item 的 align-self override 的, 所以也可以理解为, container 设置 align-items 只是一个批量操作, 为了方便而已.

align-content (container)

当设置了 flex-wrap: wrap 那么 container 的 align-items 和 item 的 align-self 都会失效. 取而代之是 container 的 align-content. (注: 无论 item 是否真的超过 container, 只要 set wrap 那么就不看 align-items 了)

gap (container)

item 之前的间距 (gutter)

 gap: 10px

flex-grow (item)

grow 表示当 container 有多余的空间时, 是否自动加大 item, 它的 direction 和 flex-direction 一致, 比如上面的例子 flex-direction 是 row, 那么 flex-grow 的 direction 就是 horizontal.

白色区域就是额外的空间. grow: 0 表示不会加大, 其余号码表示会加大, 并且它是一个比例值, 具体如何按比例分配多余空间下面会讲.

flex-shrink (item)

shrink 和 grow 相反, 它表示当 container 空间不足够时, 是否自动减小 item, 它的 direction 和 flex-direction 一致, 比如上面的例子 flex-direction 是 row, 那么 flex-grow 的 direction 就是 horizontal.

它和 grow 一样, 0 表示不能收缩, 其余号码则是比例, 具体算法下面会讲.

另外它和 wrap 是冲突的, wrap 优先. 所以 shirink 只会配上 nowrap.

flex-basis (item)

它的 direction 和 flex-direction 一致, 比如上面的例子 flex-direction 是 row, 那么 flex-basis 指的是 horizontal 也就是 width.

它的功效就是依据 direction 去替代 width / height 而已. 当 basis = auto 时, 它会直接拿 width / height 的值来用.

 

小总结

上面过了一轮 flex 的大部分属性和它的基本功用. 有个画面就好. 下面我们来逐一看看它们的细节.

要了解 Flex, 我们脑袋里要时刻有 container 和 item, 和它们的 dimension (width / height) 最后在配上各种 flex 属性.

 

Flex 属性 (逐个解释)

flex-direction

.flex-container {
  display: flex;
  flex-direction: row;
  width: 200px;
  border: 2px solid black;
}

设置 display: flex 以后, 第一步就是选方向. 

默认是 row (horizontal 横向)

效果:

虽然里面的 div 是 display block, 但是经过 flex 处理, 它就往横向走了.

flex-direction: column; 就往下走, 即使 item 是 inline 也会被强制变成往下

它还可以 reverse items 哦, row-reverse, column-reverse

 

flex-wrap

wrap 是设置当 container 装不下 item 的时候如何处理. 默认值是 nowrap.

假设 container width: 100px;

item3 跑出去了, 通过设置 flex-wrap: wrap 效果如下:

item3 没有跑出去, 反而是往下掉了. 这个在做 RWD (Responsive Web Design) 超实用的.

wrap-reverse

wrap 是往下掉, 如果想往上也是可以的. 例如下面这个例子.

 

flex-flow

flex-flow 是 flex-direction 和 flex-wrap 的 shorthand.

flex-flow: row wrap;

 

justify-content

justify-content 是用来做 alignment 的. 有点像 Figma 的这个

注: align 的前提是要有空间, 当 container 的 width, height 不是 hug content, 或者 item 的 width, height 宽度/高度不一致的时候就会产生空间, 没有空间自然就谈不上 align 了.

justify-content 调整的方向和 direction 的方向是一致的, 比如 direction 是 horizontal, 那么 justify-content 调整的就是左右.

前面 4 个是比较常用到的. 通常是写 flex-start 而不是 left 或 start, 虽然 chrome 都能接受.

 

align-items

align-items 的默认值是 stretch 拉紧的意思.

container height 50px, item height auto 的情况下

baseline 通常用于对齐字体, 如果字体一样的话, 它的效果和 flex-start 是一样的.

item 2,4 比较高, 所以产生了空间, align-items: center 的效果如上.

 

align-content

align-content 和 align-items, justify-content 差不多玩法, 

首先它只能用于 flex-wrap:wrap 的情况下, nowrap 情况下, align-content 是无视的.

当 item 超过 container, item 会被 wrap 成多排, 可以把这 2 排想象成 2 个 item, 所以它也能类似 justify-content 那样做 space-between

 

Gap

Flex 也是可以用 gap 的, 和 Grid 一样. 

它就是 Figma 的 Spacing between items, 

gap vs gutter (垄沟)

CSS Flex 和 Grid 都是用 gap, gutter 是用在 Figma 的 Grid Layout 虽然它们很相识.

column / row-gap

 

gap 表示 row 和 column 一起 set, 也可以分开配置的

gap: 8px 16px;
row-gap: 8px;
column-gap: 16px;

 

Flex Item

下面的讲解, 会假定 flex-direction 是 row. 如果是 column 请自行换取反方向理解.

Order

顾名思义, 用来修改 element 顺序, 动态排版呢

 

flex-grow

grow 用于表示 item 是否可以自行放大填满 container. grow: 0 代表不可以放大, 其余号码表示按比例分配 container 额外的空间.

参考: 知乎 – 详解 flex-grow 与 flex-shrink

上面这篇讲解的非常详细了, 而且也有很好的例子.

我讲一下它的 step 就好.

当出现额外空间时 (container width > total item width)

item 有 grow: number (非 zero) 表示可以放大

接着就是看如何分配这些额外的空间.

假设额外的空间有 500px

有 2 个 item 要分配.

grow:1, grow:1 表示平分

grow:2, grow: 1 表示 itemA 分配比例是 2:3, itemB 比例是 1:3

所以 500 x 2 / 3 = 333.33px 就是 itemA 需要扩大的 width.

注1: 当所有 grow 总数 < 1 时 (比如 grow: 0.1), 算法会不同. 最终会导致 container 没有被完全填满. (想了解详情, 看上面的参考)

注2: max-width 会导致 item 无法扩大, 那么其它 item 会继续分配掉, 直到所有 item 都 hit 到 max-width 就会导致 container 没有被完全填满.

 

flex-shrink

shrink 的分配方式和 grow 是不同的哦. 

参考: 知乎 – 详解 flex-grow 与 flex-shrink

同样这篇讲的很详细了. 我讲 step 就好.

当 container 不够大. 它就会去缩小 item. item 默认的 flex-shrink 是 1. 

0 表示不允许缩小. 

假设需要 500px 空间. 那么就要看如何分配给所有 item

如果所有 item 都是 shrink: 1 那就是按股份分配.

这里和 grow 的平均分配不一样哦.

item A,B,C 分别是 300, 200, 500px

那么持股比例就是 30%, 20%, 50% 大家都是 shrink:1 那么就按这个比例分.

500 x 30% = 150px, 所以 ItemA 要缩小 150px

那么当 shrink 不是 1 的时候呢? 算法是这样的

假设 Item A, B, C shrink 是 1, 2, 3

那么先把比例算出来. 

300 x 1 = 300

200 x 2 = 400

500 x 3 = 1500

ItemA 比例是 300: (300 + 400 + 1500) = 2200

所以 ItemA 缩小 = 500 x 300 / 2200 = 68px

注1: 当所有 shrink 总数 < 1 时 (比如 shrink: 0.1), 算法会不同. 最终会导致 item 溢出 container (想了解详情, 看上面的参考)

注2: min-width 会导致 item 无法缩小, 那么其它 item 会继续分配掉, 直到所有 item 都 hit 到 min-width 就会导致 item 溢出 container.

注3: padding, border 是不能被缩小的, 只有 avaiable width 能被缩小, 而且缩小到 item 的 min-content 就不能再小了.

 

flex-basis

参考:

What are the differences between flex-basis and width?

[译] width 与 flex-basis 的区别

flex-basis和width到底谁听谁的?

basis 就是 item 的 尺寸. 假设 container 是 flex-direction: row.

flex-basis 指的就是 width. 那么对比起设置 width 有什么区别吗? 

黄金法则: 

flex-basis 你不设置, 它就是 auto, 然后它会去拿 width. 当你设置后, 它就无视 width 了. (建议使用 flex-basis)

flex-basis 是会被 grow 和 shrink 影响的, 也会被 min/max dimension 影响.

 

flex

它是 flex-grow, flex-shrink, flex-basis 的 shorthand.

flex: 1 代表什么呢? What does flex: 1 mean?

直觉可能会以为是 flex-grow: 1

但它是 flex: 1 1 0

basis 的默认是 auto, 但是 flex: 1 = flex: 1 1 0, 所以 basic 变成了 0

 

align-self

container 有 align-items 控制所有 item

但是如果有其中一些 item 想要例外, 那么就可以使用 align-self, 达到这样的效果

container align-items: stretch,

item 3 align-self : center

 

Flex Dimension

假定, flex-direction: row

主要说一下 flex 如何影响 dimension.

width: auto

假设 item 是 display block. width: auto 那么在没有 flex 的情况下它就是 fill container.

一旦添加了 flex, item 就变成了 inline, 这时 width: auto 的效果变成了 hug content.

height: auto

在没有 flex 的情况下, item height auto 表示 hug content.

一旦添加了 flex with align-items stretch, height: auto 变成了 fill container

height: percentage

How can I make Flexbox children 100% height of their parent?

Percentage Heights in Flexbox

% 是无效的, 在 flex direction row 情况下, item height 只能 stretch 填满, 或者 hardcode px 或者 hug content.

不管你 set 1%, 50%, 100%, 它的效果相等于 align-items: flex-start.

 

Display: inline-flex

display: flex 属于 block, 如果 width: auto 相等于 100% 跟 parent 跑. 

如果想让 container hug content width (direction: row) 的话就需要用 inline flex, width: auto

inline flex 也是可以设置 width 的哦, 这个和 display: inline 不同, flex 也没有 display: inline-block-flex 这种东西.

 

Layout Grid by Flex

参考: Layout Grid by Flex

 

个人使用体验

最常拿来做 spacing. gap 取代了 margin-top

第二常用的是 align, 尤其是 center

第三常用的 RWD, 配合 wrap

RWD 方面 Flex 并没有很强, 许多时候还是需要用到 media query

比较少会用到的是控制 dimension. 比如 grow, flex-basic 等等. 如果用到的话可能也可以考虑一下 Grid 了.

 

冷知识 – 当 flex-direction: column 遇上 item margin-inline: auto

.container {
  border: 4px solid red;
  width: 300px;

  .box1 {
    padding: 1rem;
    background-color: lightblue;
    width: 100px;
    margin-inline: auto;
  }
}
View Code

在没有 flex 的情况下, item width: 100px, margin-inline: auto 的效果如上 (我们常用的居中方案)

如果 width: auto 则 margin-inline 没有效果, 如下图

但当 container 加上 flex column 以后就不同了, 下图

虽然 item width 是 auto, 但是 margin-inline 却有效果了, 它和设置 align-self: center 是一摸一样的.

注: 通常用了 flex 就不会用 margin-inline 做 alignment 了, 我是不小心发现的.

 

用例子领悟

有个 container, 左边是 title, 右边是图, 各占 50%

HTML

<div class="container">
  <div class="col-left">
    <h1>Hello,</h1>
    <h1>My name is</h1>
    <h1>Arfan</h1>
  </div>
  <div class="col-right">
    <img src="img/img1.jfif" />
  </div>
</div>

CSS 

.container {
  display: flex;
  background-color: pink;
}
.container .col-left {
  flex-grow: 1;
  background-color: blue;
}
.container .col-right {
  flex-grow: 1;
  background-color: yellow;
}

通过 flex-grow 1:1 分配 width

效果

结果发现并没有平均分配, 原因是 grow 是分配剩余的空间, 把 grow 拿掉会看见 col-left, col-right 是不一样 width.

所以正确的做法是用 

flex: 1 也等价于 flex: 1 1 0

最后一个 0 就把 basis 变成 0px 了, 这样 col-left, col-right 的 width 就相同了, 然后通过 grow 增长到填满 container, 就实现了 50%

 

posted @ 2021-12-29 20:30  兴杰  阅读(302)  评论(0编辑  收藏  举报