來源:https://ishadeed.com,作者:Ahmad Shadeed
翻譯:公眾號《前端外文精選》
如果兩個或多個元素很接近,那麼用户就會認為它們以某種方式屬於彼此。當對多個設計元素進行分組時,用户可以根據它們之間的空間大小來決定它們之間的關係。沒有間距,用户將很難瀏覽頁面並知道哪些內容相關而哪些內容無關。
在本文中,我將介紹有關CSS中的間距,實現此間距的不同方法以及何時使用 padding 或 margin 所需的所有知識。
間距類型
CSS中的間距有兩種類型,一種在元素外部,另一種在元素內部。對於本文,我將其稱為outer和inner。假設我們有一個元素,它內部的間距是inner,外部的間距是outer。

在CSS中,間距可以如下:
.element {
padding: 1rem;
margin-bottom: 1rem;
}
我使用 padding 來填充內部間距,使用 margin 來填充外部間距。很簡單,不是嗎?但是,當處理具有許多細節和子元素的組件時,這會變得越來越複雜。
margin 外部間距
它用於增加元素之間的間距。例如,在上一個示例中,我添加了 margin-bottom:1rem 在兩個堆疊的元素之間添加垂直間距。
由於可以沿四個不同的方向(top、right、 bottom、left)添加margin,因此在深入研究示例和用例之前,一定要闡明一些基本概念,這一點很重要。
margin 摺疊
簡而言之,當兩個垂直元素具有margin,並且其中一個元素的margin大於另一個元素時,發生邊距摺疊。在這種情況下,將使用更大的margin,而另一個將被忽略。

在上面的模型中,一個元素有 margin-bottom,另一個元素有 margin-top,邊距較大的元素獲勝。
為避免此類問題,建議按照本文使用單向邊距。此外,CSS Tricks還在頁邊距底部和頁邊距頂部之間進行了投票。61%的開發者更喜歡 margin-bottom 而不是 margin-top。
請在下面查看如何解決此問題:
.element:not(:last-child) {
margin-bottom: 1rem;
}
使用 :not CSS選擇器,您可以輕鬆地刪除最後一個子元素的邊距,以避免不必要的間距。
另一個與邊距摺疊相關的例子是子節點和父節點。讓我們假設如下:
<div class="parent">
<div class="child">I'm the child element</div>
</div>
.parent {
margin: 50px auto 0 auto;
width: 400px;
height: 120px;
}
.child {
margin: 50px 0;
}

請注意,子元素固定在其父元素的頂部。那是因為它的邊距摺疊了。根據W3C,以下是針對該問題的一些解決方案:
- 在父元素上添加
border - 將子元素顯示更改為
inline-block
一個更直接的解決方案是將 padding-top 添加到父元素。

負margin
它可以與四個方向一起使用以留出餘量,在某些用例中非常有用。讓我們假設以下內容:

父節點具有 padding:1rem,這導致子節點從頂部、左側和右側偏移。但是,子元素應該緊貼其父元素的邊緣。負margin可以助你一臂之力。
.parent {
padding: 1rem
}
.child {
margin-left: -1rem;
margin-right: -1rem;
margin-top: -1rem;
}
如果您想更多地挖負margin,建議閲讀這篇文章。
padding 內部間距
如前所述,padding在元素內部增加了一個內間距。它的目標可以根據使用的情況而變化。
例如,它可以用於增加鏈接之間的間距,這將導致鏈接的可點擊區域更大。

必須提出的是,垂直方向的padding對於那些具有 display:inline 的元素不適用,比如 <span> 或 <a>。如果添加了內邊距,它不會影響元素,內邊距將覆蓋其他內聯元素。
這只是一個友好的提醒,應該更改內聯元素的 display 屬性。
.element span {
display: inline-block;
padding-top: 1rem;
padding-bottom: 1rem;
}
CSS Grid 間隙
在CSS網格中,可以使用 grid-gap 屬性輕鬆在列和行之間添加間距。這是行和列間距的簡寫。

.element {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 16px; /* 為行和列都增加了16px的間隙。 */
}
gap屬性可以使用如下:
.element {
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 24px;
grid-column-gap: 16px;
}
CSS Flexbox 間隙
gap 是一個提議的屬性,將用於CSS Grid和flexbox,撰寫本文時,它僅在Firefox中受支持。
.element {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
CSS 定位
它可能不是直接的元素間距方式,但在一些設計案例中卻起到了一定的作用。例如,一個絕對定位的元素需要從其父元素的左邊緣和上邊緣定位 16px。
考慮以下示例,帶有圖標的卡片,其圖標應與其父對象的左上邊緣隔開。在這種情況下,將使用以下CSS:
.category {
position: absolute;
left: 16px;
top: 16px;
}

用例和實際示例
在這一節中,你將回顧一下在日常工作中,你在處理CSS項目時,會遇到的不同用例。
header 組件

在這種情況下,標題具有logo,導航和用户個人資料。你能猜出CSS中的間距應該如何設置嗎?好吧,讓我為你添加一個骨架模型。
<header class="c-header">
<h1 class="c-logo"><a href="#">Logo</a></h1>
<div class="c-header__nav">
<nav class="c-nav">
<ul>
<li><a href="#">...</a></li>
</ul>
</nav>
<a href="#" class="c-user">
<span>Ahmad</span>
<img class="c-avatar" src="shadeed.jpg" alt="">
</a>
</div>
</header>

Header的左側和右側都有padding,這樣做的目的是防止內容物緊貼在邊緣上。
.c-header {
padding-left: 16px;
padding-right: 16px;
}
對於導航,每個鏈接在垂直和水平側均應具有足夠的填充,因此其可單擊區域可以很大,這將增強可訪問性。
.c-nav a {
display: block;
padding: 16px 8px;
}
對於每個項目之間的間距,您可以使用 margin 或將 <li> 的 display 更改為 inline-block。內聯塊元素在它的兄弟元素之間添加了一點空間,因為它將元素視為字符。
.c-nav li {
/* 這將創建你在骨架中看到的間距 */
display: inline-block;
}
最後,頭像(avatar)和用户名的左側有一個空白。
.c-user img,
.c-user span {
margin-left: 10px;
}
請注意,如果你要構建多語言網站,建議使用如下所示的CSS邏輯屬性。
.c-user img,
.c-user span {
margin-inline-start: 1rem;
}

請注意,分隔符周圍的間距現在相等,原因是導航項沒有特定的寬度,而是具有padding。結果,導航項目的寬度基於其內容。以下是解決方案:
- 設置導航項目的最小寬度
- 增加水平padding
- 在分隔符的左側添加一個額外的margin
最簡單,更好的解決方案是第三個解決方案,即添加 margin-left。
.c-user {
margin-left: 8px;
}
網格系統中的間距:Flexbox
網格是間隔最常用的情況之一。考慮以下示例:

間距應在列和行之間。考慮以下HTML標記:
<div class="wrapper">
<div class="grid grid--4">
<div class="grid__item">
<article class="card"><!-- Card content --></article>
</div>
<div class="grid__item">
<article class="card"><!-- Card content --></article>
</div>
<!-- And so on.. -->
</div>
</div>
通常,我更喜歡將組件封裝起來,並避免給它們增加邊距。由於這個原因,我有 grid__item元素,我的card組件將位於其中。
.grid--4 {
display: flex;
flex-wrap: wrap;
}
.grid__item {
flex-basis: 25%;
margin-bottom: 16px;
}
使用上述CSS,每行將有四張卡片。這是在它們之間添加空格的一種可能的解決方案:
.grid__item {
flex-basis: calc(25% - 10px);
margin-left: 10px;
margin-bottom: 16px;
}
通過使用CSS calc() 函數,可以從 flex-basis 中扣除邊距。如你所見,這個方案並不是那麼簡單。我比較喜歡的是下面這個辦法。
- 向網格項目添加
padding-left - 在網格父節點上增加一個負值
margin-left,其padding-left值相同。
幾年前,我從CSS Wizardy那裏學到了上述解決方案(我忘記了文章標題,如果您知道,請告訴我)。
.grid--4 {
display: flex;
flex-wrap: wrap;
margin-left: -10px;
}
.grid__item {
flex-basis: 25%;
padding-left: 10px;
margin-bottom: 16px;
}
我之所以用了負 margin-left,是因為第一張卡有 padding-left,而實際上不需要。所以,它將把 .wrapper 元素推到左邊,取消那個不需要的空間。
另一個類似的概念是在兩邊都添加填充,然後邊距為負。這是Facebook故事的一個示例:

.wrapper {
margin-left: -4px;
margin-right: -4px;
}
.story {
padding-left: 4px;
padding-right: 4px;
}
網格系統中的間距:CSS Grid
現在,到了激動人心的部分!使用CSS Grid,你可以很容易地使用 grid-gap 添加間距。此外,你不需要關心網格項的寬度或底部空白,CSS Grid 為你做者一切!
.grid--4 {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 1rem;
}
就是這樣!難道不是那麼容易和直接嗎?
按需定製
我真正喜歡CSS Grid 的地方是 grid-gap 只在需要的時候才會被應用。考慮下面的模型。

沒有CSS網格,就不可能擁有這種靈活性。首先,請參見以下內容:
.card:not(:last-child) {
margin-bottom: 16px;
}
@media (min-width: 700px) {
.card:not(:last-child) {
margin-bottom: 0;
margin-left: 1rem;
}
}
不舒服吧?這個如何?
.card-wrapper {
display: grid;
grid-template-columns: 1fr;
grid-gap: 1rem;
}
@media (min-width: 700px) {
.card-wrapper {
grid-template-columns: 1fr 1fr;
}
}
完成了!容易得多。
處理底部margin
假設以下組件堆疊在一起,每個組件都有底邊距。

注意最後一個元素有一個空白,這是不正確的,因為邊距只能在元素之間。
可以使用以下解決方案之一解決此問題:
解決方案1-CSS :not 選擇器
.element:not(:last-child) {
margin-bottom: 16px;
}
解決方案2:相鄰兄弟組合器
.element + .element {
margin-top: 16px;
}
雖然解決方案1具有吸引力,但它具有以下缺點:
- 它會導致CSS的特異性問題。在使用
:not選擇器之前不可能覆蓋它。 - 萬一設計中有不止一列,它將無法正常工作。參見下圖。

關於解決方案2,它沒有CSS特異性問題。但是,它只能處理一個列棧。
更好的解決方案是通過向父元素添加負邊距來取消不需要的間距。
.wrapper {
margin-bottom: -16px;
}
它用一個等於底部間距的值將元素推到底部。注意不要超過邊距值,因為它會與同級元素重疊。
Card組件
Oh,如果我想把所有細節的Card組件間距都寫進去的話,最後可能會出現書本上的內容。我就突出一個大概的模式,看看間距應該如何應用。

你能想到此卡片在哪裏使用間距嗎?參見下圖。

<article class="card">
<a href="#">
<div class="card__thumb"><img src="food.jpg" alt=""></div>
<div class="card__content">
<h3 class="card__title">Cinnamon Rolls</h3>
<p class="card__author">Chef Ahmad</p>
<div class="card__rating"><span>4.9</span></div>
<div class="card__meta"><!-- --></div>
</div>
</a>
</article>
.card__content {
padding: 10px;
}
上面的 padding 將向其中的所有子元素添加一個偏移量。然後,我將添加所有邊距。
.card__title,
.card__author,
.card__rating {
margin-bottom: 10px;
}
對於評分和 .car__meta 元素之間的分隔線,我將添加它作為邊框。
.card__meta {
padding-top: 10px;
border-top: 1px solid #e9e9e9;
}
糟糕!由於對父元素 .card__content 進行了填充,因此邊框沒有粘在邊緣上。

是的,你猜對了!負邊距是解決辦法。
.card__meta {
padding-top: 10px;
border-top: 1px solid #e9e9e9;
margin: 0 -10px;
}
糟糕,再次!出了點問題。內容粘在邊緣!

為了解決這個問題,內容應該從左右兩邊加墊(呵呵,看來加墊是個新詞)。
.card__meta {
padding: 10px 10px 0 10px;
border-top: 1px solid #e9e9e9;
margin: 0 -10px;
}

文章內容
我相信這是一個非常非常普遍的用例。由於文章內容來自CMS(內容管理系統),或者是由Markdown文件自動生成的,因此無法為元素添加類。
考慮下面的示例,其中包含標題,段落和圖像。
<div class="wrapper">
<h1>Spacing Elements in CSS</h1>
<p><!-- content --></p>
<h2>Types of Spacing</h2>
<img src="spacing-1.png" alt="">
<p><!-- content --></p>
<p><!-- content --></p>
<h2>Use Cases</h2>
<p><!-- content --></p>
<h3>Card Component</h3>
<img src="use-case-card-2.png" alt="">
</div>
為了使它們看起來不錯,間距應保持一致並謹慎使用。我從type-scale.com借了一些樣式。
h1, h2, h3, h4, h5 {
margin: 2.75rem 0 1.05rem;
}
h1 {
margin-top: 0;
}
img {
margin-bottom: 0.5rem;
}

如果一個 <p> 後面有一個標題,例如“Types of Spacing”,那麼 <p> 的 margin-bottom 將被忽略。你猜到了,那是因為頁邊距摺疊。
Just In Case Margin
我喜歡把這個叫做 "Just in case" margin,因為這就是字面意思。考慮一下下面的模型圖。

當元素靠近的時候,它們看起來並不好看。我是用flexbox搭建的。這項技術稱為“對齊移位包裝”,我從CSS Tricks中學到了它的名稱。
.element {
display: flex;
flex-wrap: wrap;
}
當視口尺寸較小時,它們的確以新行結尾。見下文:

需要解決的是中間設計狀態,即兩件物品仍然相鄰,但兩件物品之間的間距為零的設計狀態。在這種情況下,我傾向於向元素添加一個 margin-right,這樣可以防止它們相互接觸,從而加快 flex-wrap 的工作速度。

CSS 書寫模式
根據MDN:
writing-mode CSS屬性設置了文本行是水平還是垂直排列,以及塊的前進方向。
你是否曾經考慮過將邊距與具有不同 writing-mode 的元素一起使用時應如何表現?考慮以下示例。

.wrapper {
/* 使標題和食譜在同一行 */
display: flex;
}
.title {
writing-mode: vertical-lr;
margin-right: 16px;
}
標題被旋轉了90度,在它和圖像之間應該有一個空白區。結果表明,基於 writing-mode 的頁邊距工作得非常好。
我認為這些用例就足夠了。讓我們繼續一些有趣的概念!
組件封裝
大型設計系統包含許多組件。向其直接添加邊距是否合乎邏輯?
考慮以下示例。

<button class="button">Save Changes</button>
<button class="button button-outline">Discard</button>
按鈕之間的間距應在哪裏添加?是否應將其添加到左側或右側按鈕?也許你可以如下使用相鄰同級選擇器:
.button + .button {
margin-left: 1rem;
}
這是不好的。如果只有一個按鈕的情況怎麼辦?或者,當它垂直堆疊時在移動設備上將如何工作?很多很多的複雜性。
使用抽象組件
解決上述問題的一種方法是使用抽象的組件,其目標是託管其他組件,就像Max Stoiber所説的那樣,這是將管理邊距的責任移到了父元素上,讓我們以這種思維方式重新思考以前的用例。

<div class="list">
<div class="list__item">
<button class="button">Save Changes</button>
</div>
<div class="list__item">
<button class="button button-outline">Discard</button>
</div>
</div>
注意,我添加了一個包裝器,並且每個按鈕現在都包裝在其自己的元素中。
.list {
display: flex;
align-items: center;
margin-left: -1rem; /* 取消第一個元素的左空白 */
}
.list__item {
margin-left: 1rem;
}
就是這樣!而且,將這些概念應用到任何JavaScript框架中都相當容易。例如:
<List>
<Button>Save Changes</Button>
<Button outline>Discard</Button>
</List>
你使用的JavaScript工具應該將每個項包裝在自己的元素中。
間隔組件
是的,你沒看錯。我在這篇文章中討論了避免margin的概念,並使用間隔組件來代替它們。
讓我們假設一個區域需要從左到右24px的空白,並記住這些限制:
- margin不能直接用於組件,因為它是一個已經構建的設計系統。
- 它應該是靈活的。間距可能在X頁上,但不在Y頁上。
我在檢查Facebook的新設計CSS時首先注意到了這一點。

那是一個 <div>,內聯樣式寬度:16px,它唯一的作用是在左邊緣和包裝器之間增加一個空白空間。
引述這本React遊戲手冊中的內容。
但在現實世界中,我們確實需要組件之外的間距來合成頁面和場景,這就是margin滲入組件代碼的地方:用於組件的間距組合。
我同意。對於大型設計系統,不斷向組件添加margin是不可伸縮的。這將最終導致一個令人毛骨悚然的代碼。
間隔組件的挑戰
現在你瞭解了間隔組件的概念,讓我們深入研究使用它們時遇到的一些挑戰。這是我想到的一些問題:
- 間隔組件如何在父級內部取其寬度或高度?在水平佈局和垂直佈局中,它將如何工作?
- 我們是否應該根據其父項的顯示類型(Flex,Grid)對它們進行樣式設置
讓我們一一解決上述問題。
調整間隔組件的大小
可以創建一個接受不同變化和設置的間隔。我不是JavaScript開發人員,但我認為他們將其稱為Props。考慮來自styled-system.com的以下內容:
我們在一個header和一個 section之間有一個隔板。
<Header />
<Spacer mb={4} />
<Section />
雖然這個有點不一樣,一個間隔器在logo和導航之間建立一個自動間隔。
<Flex>
<Logo />
<Spacer m="auto" />
<Link>Beep</Link>
<Link>Boop</Link>
</Flex>
你可能會認為,通過添加 justify-content:space-between,使用CSS做到這一點相當容易。
如果設計上需要改一下怎麼辦?那麼,如果是這樣的話,樣式就應該改了。
見下文,你看到那裏的靈活性了嗎?
<Flex>
<Logo />
<Link>Beep</Link>
<Link>Boop</Link>
<Spacer m="auto" />
<Link>Boop</Link>
</Flex>
那麼,如果是這樣的話,就應該改變樣式。你看出來有什麼靈活性了嗎?對於尺寸調整部分,可以根據其母體的尺寸調整間隔的尺寸。
對於上面的內容,也許你可以做一個叫 grow 的prop,可以計算成 flex-grow:1 在CSS中。
<Flex>
<Spacer grow="1" />
</Flex>
使用偽元素
我考慮過的另一個想法是使用偽元素創建間隔符。
.element:after {
content: "";
display: block;
height: 32px;
}
也許我們可以選擇通過一個偽元素而不是一個單獨的元素來添加間隔器?例如:
<Header spacer="below" type="pseudo" length="32">
<Logo />
<Link>Home</Link>
<Link>About</Link>
<Link>Contact</Link>
</Header>
直到今天,我還沒有在項目中使用間隔組件,但是我期待可以使用它們的用例。
CSS數學函數:Min(),Max(),Clamp()
有可能有動態的邊距嗎?例如,根據視口寬度設置具有最小值和最大值的空白。答案是肯定的!我們可以。最近,Firefox 75支持CSS數學函數,這意味着根據CanIUse在所有主流瀏覽器中都支持CSS數學函數。
讓我們回想一下Grid用例,以瞭解如何在其中使用動態間距。
.wrapper {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: min(2vmax, 32px);
}
下面是 min(2vmax,32px) 的意思:使用一個等於 2vmax 的間隙,但不能超過 32px。

擁有這樣的靈活性確實令人驚訝,並且為我們提供了構建更多動態和靈活佈局的許多可能性。
文章首發《前端外文精選》微信公眾號
繼續閲讀其他高贊文章
- 2020年的12個Vue.js開發技巧和竅門
- 【小技巧】CSS如何實現文字兩端對齊效果?
- 7個簡單但棘手的JavaScript面試問題
- 讓你可以在2020年成為前端大師的9個項目
- 【實戰】Vue.js 圖標選擇組件開發
- 你必須知道的HTTP基本概念
- 【筆記】Web全棧工程師的自我修養(上)
- 【筆記】Web全棧工程師的自我修養(下)
- 【小技巧】H5頁面上如何禁止手機虛擬鍵盤彈出?
- 拒絕JavaScript,這三個CSS技巧你一定用的上
- 7個能提高你生產力的隱藏Chrome DevTools功能
- 【圖文教程】同步你的VSCode設置及擴展插件,換機不用愁
- 更多...