[CSS] Flex/Grid Layout Modules, part 2

本篇会有不少冷门范例。

其实我觉得很奇妙,就是我老是踩到一些超冷门连 Google 都找不太到的雷。


Flexbox 能与不能

首先还是得叮咛一下,如果使用静态定位样式,会打破 Flex 容器轴流向,所以请不要觉得用了 position: absolute, position: static, position: fixedposition: sticky 的时候来问为何 Flexbox 没有效果。


尺寸

无论是 Flex 容器或是 Flex 元件,尺寸这件事情在设计或是设定上都会有一定的 毛病 限制,首先我们先来看看 Flex 容器的尺寸。

在各种 CSS 框架上,预设的容器轴都以 row 为主,而这样的容器可能会遇到的状况会有哪些呢?

  • 容器的剩余空间不如预期。
  • 对齐点的状况不如预期。
  • Flex 元件超出容器。
  • Flex 元件压缩不如预期。
  • Flex 元件强迫换行的状况。
  • writing-mode 的影响。

当然,如果换为 column 为主轴,大概会追加以下状况,

  • Flex 元件无法换行。
  • flex-basis 设定不如预期。
  • stretch 设定不如预期。

首先,关於容器尺寸这件事情,我们在 row 的情况下,由於我们的内容边界都是以显示器边界为主,所以,你在以 row 的设定状况下,并不用特别担心 width 的状况。但是,由於 nowrap 是一个预设的行为,所以通常会遇到 Flex 元件超出去的情形。

此时,如果你的容器搭配了 overflow: hidden 的话,那麽元件本身就会被容器切断而无法完整显示。这个情况跟一般使用区块元件的情形是相同的。

https://ithelp.ithome.com.tw/upload/images/20210906/20001433D0fioIg8L0.png

在这个时候,倘若你的 Flex 元件有设定 flex-shrink 的话,那麽在可允许的范围内,Flex 元件就会被压缩到主轴尺寸内。

请注意,是可允许的范围内。

为什麽要这样说?因为 flex-shrink 在压缩元件的情况下,无法压缩到比 content 这个尺寸还要小的尺寸,举个例子来说,

.flex-item {
    flex: 1 1 10rem;
}

https://ithelp.ithome.com.tw/upload/images/20210906/20001433E6exWTyXc0.png

在这种情况下,你可以搭配 max-width 来限制 Flex 元件的大小,不过这有个但书,我们容後再述,

.flex-item {
    flex: 1 1 20rem;
    max-width: 20rem;
}

https://ithelp.ithome.com.tw/upload/images/20210906/200014333rxrqgXVXF.png

总结来说,当你的内容会超出元件宽度时,你的 flex-shrink 会将内容尺寸当作是最小尺寸来使用。当然,我们可以将内容强迫断行来达成显示正常的目的。

.flex-item {
    flex: 1 1 20rem;
    word-break: break-all;
}

接着来说一下刚刚的但书。我们在大多数的 CSS 框架中(以 Bootstrap 5.0.x 为例),其实比较常见的设定会类似这样,

.col-sm-3 {
    flex: 0 0 auto;
    width: 25%;
}

通常是不使用 flex-shrink, flex-grow 这个设定的,也就是说不要压缩、不扩充元件。所以,当你想要压缩元件,又想搭配 flex-grow 的话,那麽这个 max-width 会是一个很难处理的事情。简单来说,你会遇到这样的状况,

  • 容器放大时,flex-grow 会踩到 max-width 而停止填满容器。
  • 容器缩小时,flex-shrink 因为内容宽度,也会踩到 max-width 而产生非预期尺寸。
  • 以上的问题换成 min-width 也会有类似的状况。

所以,总括来说,Flex 元件请单纯拿来排版就好,不然各大 CSS 框架也不会将 Flex 元件的样式写的那麽简单,以 Bootstrap 5.0.x 来说,就只做一件事情,

width 硬干 flex-basis,其他都停用。

也由於预设不要压缩、不扩充元件的关系,所以大多数框架都会设定 flex-wrap: wrap 来让 Flex 元件能够断行。当然,断行也是会有断行的问题(笑)。

对了,我好心提醒一下,有在用 Bulma.io 的人,他预设是 fex: 1 1 0;,另外 Windi CSSflex-1 预设是 flex: 1 1 0%,所以上面讲的状况他都会遇到喔(啾咪)。

我没有要酸 Bootstrap 的意思,後面讲到 Grid 我也会拿 tailwindcss 来嘴一下。

ps. Bootstrap 5.1.0 之後也开始使用 Grid Layout Module 了(啾咪)。


留白与断行

首先我们来说说断行这件事情,在 flex-wrap: wrap 的设定下,任何超过容器宽度的元件都会被换行。然而,以 flex-direction: row 的设定来说,如果没有特别指定容器宽度的话,基本上就是以装置的宽度来看。

好的,那麽断行会受到哪些行为的影响呢?

  • flex-basis
  • width, min-width, max-width
  • margin, padding
  • 容器拟似元件 ::before, ::after
  • box-sizingborder-box 下,border 会产生影响

在一般的情况下,我们容器基本上是没有间隔(gap)的。我在本系列文章的图片仅是为了识别方便而将每个 Flex 元件与容器之间加上间隔。正常来说,这是我们的 Flex 真实样貌,

https://ithelp.ithome.com.tw/upload/images/20210906/2000143323IObUhwK2.png

由於我们使用的是 box-sizing: border-box,所以 Flex 元件的尺寸包含了框线。如果你使用的是 content-box 的话,那麽 border 就必须纳入考虑。在此就不多说这个老掉牙的设定了。

回到 Flex 容器上,我们先来看看留白(padding)这件事情。倘若你在 Flex 容器中使用了 padding 样式,那麽你的轴尺寸就必须要扣掉样式值,这个理论上来说 应该是常识。基於这个常识,如果你的 Flex 元件使用了绝对尺寸,那麽请确保呈现结果是符合你的需求的。

同样是留白,我们再来看 Flex 元件上面的留白(margin)。通常我们在区块元件(Box Model)或同等於区块格式的内容上,所谓的留白(margin)会有一个特性,

垂直方向相邻元件的 margin 会收合(Collapse)。

关於 margin 各种收合的事情,可以参考这一篇文章,写得非常详尽。

The Rules of Margin Collapse

回到 Flex 元件上,在 Flex 元件上面使用 margin 就没有这种 效果 了,原因只有一个,

Flex 元件并不是区块格式。

Flexible is flex formatting context, not block formatting context.

所以,即便你在 Flex 元件上宣告 display: block 也是没有什麽效果的,他不会因此而转变为 Block formatting context。

也就因为如此,所以当你在设定 Flex 元件尺寸时,就必须把 margin 纳入考虑,否则他会在一些很奇怪的地方出现断行。

https://ithelp.ithome.com.tw/upload/images/20210906/20001433KfOdkjjF7u.png

所以,如果你真的想要使用 margin 来推开 Flex 元件的话,在 flex-basis 或是 width, max-width 当中,请使用计算宽度来达成你的目的。

.flex-item {
    flex: 0 0 auto;
    
    // 不要问我为什麽 - 40px
    width: calc(50% - 40px);

    // 使用 margin 来留白
    margin: 0 20px;
}

但是,不太建议这麽做。

原因用图片来解释比较清楚,

https://ithelp.ithome.com.tw/upload/images/20210906/20001433kT3BX99y8p.png

由於 Flex 容器本身没有间隔(gap)的概念,所以倘若想使用 margin 的方式来制作这个间隔,就会产生相邻元件间隔较大的结果。所以你在各大框架当中,几乎没有人会在 Flex 元件中使用 margin 留白来产生间隔。另,关於 gap 这件事情我後面会提到。

当然,特殊位移需求除外,例如 offset-3 这种。

最後,回到 Flex 容器本身,倘若你使用拟似元件,请把他当成 Flex 元件来处理。


不如预期的部分

其实上面已经讲了不少,接下来这些可能比较冷门一点,大家可以加减听听看。

  • 垂直方向(column)的换行
  • flex-basis: 100% 真的可以强迫换行吗?
  • stretch 怎麽了?
  • 关於 writing-mode 这件事

首先,打从一开始我们就只说明了 flex-direction: row 这件事情,现在来讲一些关於 flex-direction: column 的事情。故名思义就是主轴、交叉轴对调。

那麽,我前面有提到,这两个轴线对调的话,那麽有些属性应用的方向也会跟着轴转动。所以说,上面那些 水平 方向会发生影响的事情,在这个 垂直 方向上面,也一样会受到影响。

垂直方向最常遇到的就是断行问题。

断行不是问题,问题是没有固定高度。

https://ithelp.ithome.com.tw/upload/images/20210906/20001433G4H1RQukM9.png

然而,平常我们在设计页面时,所谓的「高度」这件事情并不是一个固定的尺寸,换句话说,你的容器就没有所谓的 主要轴尺寸 这件事情。换个角度来看,就是你拥有一个「无限宽」的萤幕,然後你的内容理所当然的就 无法断行

同样的道理,虽然我们可以在 row 当中使用 flex-basis: 100%; 来断行,当你将主要轴旋转为 column 时,在没有固定主要轴尺寸的状况下,所谓的 flex-basis: 100% 基本上是无效的。这一点就如同,你在一个动态高度的内容当中,设定 height: 100% 是没有意义的意思是相同的。

至於有没有其他解法?有的。

我之前写过相关的文章 [CSS] 瀑布流难题

老文章就不拉过来占版面了,後续还是会有机会提到相关的事情。

接下来我们来聊聊 stretch 这件事情。他主要会被应用的 方向 都是发生在交叉轴上面,所以无论你的方向是 rowcolumn,请注意这个关键字都是应用在交叉轴上。

所有应用在交叉轴上的东西都有一个必须存在的东西,

交叉轴尺寸

所以,一旦你在没有定义交叉轴尺寸的 Flex 容器内,无论是 align-content: stretch; 或 Flex 元件的 align-self: stretch;,都无法产生任何效果。即便你的容器符合所谓的多行(或多栏)的情况,只要交叉轴没有固定尺寸,他就不会产生任何效果。

另外,若你使用了 align-self 的话,请与 align-items 搭配使用,效果会比较显着一点(笑)。

https://ithelp.ithome.com.tw/upload/images/20210906/200014335vsqIJUk2n.png

最後,来稍微提一下 writing-mode 这个样式。这个样式其实相当冷门,多数是使用在双位元字的情况下,详细的说明可以参考官方的文件。

CSS Writing Modes Level 3

那麽到底跟 Flex 有什麽关系呢?具体来说,writing-mode 是决定文字流的样式设定,但是,他也会干涉在 Flex 容器内的元件流向,举例来说,

https://ithelp.ithome.com.tw/upload/images/20210906/20001433eAUhKDnXNk.png

当你在容器内加上 writing-mode: vertical-lr 之後,文字流向就会改变,同时,你的 Flex 元件流向也会跟着改变。在这个情况下,你的 Flex 容器会有以下改变:

  1. 主要轴、交叉轴与 Flex 容器流向相同。
  2. 文字流向会跟着 writing-mode 样式。
  3. Flex 元件流向也会跟着 writing-mode
  4. 承上,Flex 容器对於元件的轴流向应用会交换。
  5. 承上,Flex 元件流向对容器轴的应用样式也会交换。

具体来说,加上 writing-mode: vertical-lr 跟你直接写 flex-direction: column,对於 Flex 容器(或元件)来说,没有什麽太大的区别(毕竟该交换的都会交换)。听起来很复杂,当然在没有其必要性的状况下,也不建议你这样做。当然,如果要做一些奇技淫巧的时候是可以这样写啦(笑)。


小记

这边有一个地方可以玩一下 Flexbox 的各种设定,

Flexbox Playground

https://codepen.io/enxaneta/full/adLPwv

其实不要把 Flex 想得太复杂或是很万能,他就是一个让你规划跟结构有关的事情,回到 HTML5 的本意,你该是用 table 的地方还是请你用 table 就好,硬是要用 div 画出一个表格来也不是不行,但明明就是用 table 很方便的事情,为何还要用 Flexbox 来画呢?


目录与小节:
[CSS] Flex/Grid Layout Modules, part 1


部落格同步放送:
[CSS] Flex/Grid Layout Modules, part 2


<<:  XML Parsers

>>:  Day7 - 程序设计报价 (二) - 重新定义甲乙关系

有线上网:ADSL、Cable Modem 和光纤网路差在哪里?

知道了 ISP 所提供的服务後,我们来深入一点聊聊这些服务的基本原理,以及差别在哪里吧! 依序来看看...

用React刻自己的投资Dashboard Day13 - 制作分页(Pagination)功能

tags: 2021铁人赛 React 一般的内容网站通常都会有将内容分页或是动态读取的功能,例如像...

D16/ 所以到底为什麽 remember 是 composable function? - @Composable 是什麽 part 2

今天大概会聊到的范围 compose runtime compose compiler 今天会更深...

Day 4 - Just In Time (JIT) 即时模式

JIT 即时模式 继上一篇提到开启 JIT 模式有许多优点,今天威尔猪就来浅谈这个有点厉害的新即时编...

【在 iOS 开发路上的大小事-Day21】透过 Firebase 来管理使用者 (Sign in with Apple 篇) Part1

▲ 图片取自网路 前情提要 Sign in with Apple 是 Apple 在 WWDC 2...