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?
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?
% 是无效的, 在 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
个人使用体验
最常拿来做 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; } }
在没有 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%