CSS Flexbox 裡元素的 margin 不會重疊

前陣子在動手弄 css 的東西,才注意到 flex 有很多不太一樣的設計,對於學過「以前的標準」的人來說會比較意外,就像標題提到的...

我在 JS Fiddle 上面做了一個範例可以測試,裡面有兩組 container,第一組就很標準的 div 設定為 100x100 的大小,然後 margin 設為 10px

可以看到第一組裡面,上下 block 的 margin 會「疊」起來 (也就是上面 block 的 margin-bottom 與下面 block 的 margin-top 疊起來),變成 10px,而不是分開算的 20px

這個特性可以用「margin collapsing」查到,在 MDN 上甚至有一篇「Mastering margin collapsing」可以看,而這個特性對於老人來說已經用習慣了...

但如果用 flex 實作時 (display: flex; 以及 flex-direction: column;),會發現所有的 margin 都是自己計算而不會疊加,這點在 W3C 的文件「CSS Flexible Box Layout Module Level 1」裡面有提到:

For example, floats do not intrude into the flex container, and the flex container’s margins do not collapse with the margins of its contents.

這個特性無法被改變,取而代之的是用 gap 這個 property 設定元素之間的間格。

語意化的 CSS 設定 (Contextual awareness) 減少 side effect

前幾天在 Hacker News 上看到 CSS-Tricks 上的文章「You want enabling CSS selectors, not disabling ones」這篇,在講 CSS 的設計問題,對應的 Hacker News 討論在「You want enabling CSS selectors, not disabling ones (css-tricks.com)」這邊。

文章裡面引用文章裡面提到的文章也都蠻值得看的:「You want enabling CSS selectors, not disabling ones (2021/03/08)」、「Axiomatic CSS and Lobotomized Owls (2014/10/21)」。

其中 2014 年那篇居然是 A List Apart 上的文章,好久沒看到了這個站了... 也發現居然不在 RSS/Atom feed 清單裡面,重新訂起來。

這邊拿 A List Apart 上面的圖來說明,出自「CONTEXTUAL AWARENESS」這個段落的例子。

在很多段落時,我們常使用 margin-top (或是 margin-bottom,例子可以自己變換) 來設定間距,也就是 (a) 的例子。但可以看到第一個元素就會「多出來」:

A List Apart 裡面提到的解法是 * + * (或是 p + p,看你怎麼選 CSS selector),也就是前面有相鄰的元素才需要設定 margin-top

回到 CSS-Tracks 上的文章,有些人會這樣指定 CSS (這邊用 margin-bottom,所以搭搭配的是 :last-child):

.card {
  margin-bottom: 1rem;
}

/* Wait but not on the last one!! */
.parent-of-cards :last-child {
  margin-bottom: 0;
}

也就是全部都先加上 margin-bottom,然後針對最後一個元素拿掉 margin-bottom。而另外的版本則是:

.card:not(:last-child) {
  margin-bottom: 1rem;
}

或是:

/* Only space them out if they stack */
.card + .card {
  margin-top: 1rem;
}

這樣就不用蓋來蓋去,可以降低 side effect:margin-bottom 可能會在其他地方指定,你設為 0 可能是不對的值;另外寫成兩組時 CSS 的優先順序其實是不同的,Mozilla 的 Specificity 可以參考,Specifishity 這個網站給了很有趣的 cheatsheet (你要先了解才能當 cheatsheet 用):

在文章最後面有提到 gap 這個用法,查了一下「CSS property: gap: Supported in Grid Layout」,看起來現代的瀏覽器應該是都支援了,不過如果要支援舊的瀏覽器的話就是問題...

另外順便提一下,早期大家會偏好用 + 是因為 IE7+,而 :last-child 則是 IE9+ 了:「CSS Selectors and Pseudo Selectors and browser support」。雖然現在看起都是時代的眼淚了,但可以了解一下 2014 年的時候為什麼會偏好 + 的設計。