來源: https://ishadeed.com,做者:Ahmad Shadeed
翻譯:公衆號《前端外文精選》
若是兩個或多個元素很接近,那麼用戶就會認爲它們以某種方式屬於彼此。當對多個設計元素進行分組時,用戶能夠根據它們之間的空間大小來決定它們之間的關係。沒有間距,用戶將很難瀏覽頁面並知道哪些內容相關而哪些內容無關。javascript
在本文中,我將介紹有關CSS中的間距,實現此間距的不一樣方法以及什麼時候使用 padding 或 margin 所需的全部知識。css
CSS中的間距有兩種類型,一種在元素外部,另外一種在元素內部。對於本文,我將其稱爲outer和inner。假設咱們有一個元素,它內部的間距是inner,外部的間距是outer。html
在CSS中,間距能夠以下:前端
.element { padding: 1rem; margin-bottom: 1rem; }
我使用 padding
來填充內部間距,使用 margin
來填充外部間距。很簡單,不是嗎?可是,當處理具備許多細節和子元素的組件時,這會變得愈來愈複雜。java
它用於增長元素之間的間距。例如,在上一個示例中,我添加了 margin-bottom:1rem
在兩個堆疊的元素之間添加垂直間距。web
因爲能夠沿四個不一樣的方向(top、right、 bottom、left)添加margin,所以在深刻研究示例和用例以前,必定要闡明一些基本概念,這一點很重要。面試
簡而言之,當兩個垂直元素具備margin,而且其中一個元素的margin大於另外一個元素時,發生邊距摺疊。在這種狀況下,將使用更大的margin,而另外一個將被忽略。segmentfault
在上面的模型中,一個元素有 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
添加到父元素。
它能夠與四個方向一塊兒使用以留出餘量,在某些用例中很是有用。讓咱們假設如下內容:
父節點具備 padding:1rem
,這致使子節點從頂部、左側和右側偏移。可是,子元素應該緊貼其父元素的邊緣。負margin能夠助你一臂之力。
.parent { padding: 1rem } .child { margin-left: -1rem; margin-right: -1rem; margin-top: -1rem; }
若是您想更多地挖負margin,建議閱讀這篇文章。
如前所述,padding在元素內部增長了一個內間距。它的目標能夠根據使用的狀況而變化。
例如,它能夠用於增長連接之間的間距,這將致使連接的可點擊區域更大。
必須提出的是,垂直方向的padding對於那些具備 display:inline
的元素不適用,好比 <span>
或 <a>
。若是添加了內邊距,它不會影響元素,內邊距將覆蓋其餘內聯元素。
這只是一個友好的提醒,應該更改內聯元素的 display
屬性。
.element span { display: inline-block; padding-top: 1rem; padding-bottom: 1rem; }
在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; }
gap
是一個提議的屬性,將用於CSS Grid和flexbox,撰寫本文時,它僅在Firefox中受支持。
.element { display: flex; flex-wrap: wrap; gap: 16px; }
它可能不是直接的元素間距方式,但在一些設計案例中卻起到了必定的做用。例如,一個絕對定位的元素須要從其父元素的左邊緣和上邊緣定位 16px
。
考慮如下示例,帶有圖標的卡片,其圖標應與其父對象的左上邊緣隔開。在這種狀況下,將使用如下CSS:
.category { position: absolute; left: 16px; top: 16px; }
在這一節中,你將回顧一下在平常工做中,你在處理CSS項目時,會遇到的不一樣用例。
在這種狀況下,標題具備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。結果,導航項目的寬度基於其內容。如下是解決方案:
最簡單,更好的解決方案是第三個解決方案,即添加 margin-left
。
.c-user { margin-left: 8px; }
網格是間隔最經常使用的狀況之一。考慮如下示例:
間距應在列和行之間。考慮如下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,你能夠很容易地使用 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; } }
完成了!容易得多。
假設如下組件堆疊在一塊兒,每一個組件都有底邊距。
注意最後一個元素有一個空白,這是不正確的,由於邊距只能在元素之間。
可使用如下解決方案之一解決此問題:
解決方案1-CSS :not
選擇器
.element:not(:last-child) { margin-bottom: 16px; }
解決方案2:相鄰兄弟組合器
.element + .element { margin-top: 16px; }
雖然解決方案1具備吸引力,但它具備如下缺點:
:not
選擇器以前不可能覆蓋它。
關於解決方案2,它沒有CSS特異性問題。可是,它只能處理一個列棧。
更好的解決方案是經過向父元素添加負邊距來取消不須要的間距。
.wrapper { margin-bottom: -16px; }
它用一個等於底部間距的值將元素推到底部。注意不要超過邊距值,由於它會與同級元素重疊。
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,由於這就是字面意思。考慮一下下面的模型圖。
當元素靠近的時候,它們看起來並很差看。我是用flexbox搭建的。這項技術稱爲「對齊移位包裝」,我從CSS Tricks中學到了它的名稱。
.element { display: flex; flex-wrap: wrap; }
當視口尺寸較小時,它們的確以新行結尾。見下文:
須要解決的是中間設計狀態,即兩件物品仍然相鄰,但兩件物品之間的間距爲零的設計狀態。在這種狀況下,我傾向於向元素添加一個 margin-right
,這樣能夠防止它們相互接觸,從而加快 flex-wrap
的工做速度。
根據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的空白,並記住這些限制:
我在檢查Facebook的新設計CSS時首先注意到了這一點。
那是一個 <div>
,內聯樣式寬度:16px
,它惟一的做用是在左邊緣和包裝器之間增長一個空白空間。
引述這本React遊戲手冊中的內容。
但在現實世界中,咱們確實須要組件以外的間距來合成頁面和場景,這就是margin滲入組件代碼的地方:用於組件的間距組合。
我贊成。對於大型設計系統,不斷向組件添加margin是不可伸縮的。這將最終致使一個使人不寒而慄的代碼。
如今你瞭解了間隔組件的概念,讓咱們深刻研究使用它們時遇到的一些挑戰。這是我想到的一些問題:
讓咱們一一解決上述問題。
能夠建立一個接受不一樣變化和設置的間隔。我不是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>
直到今天,我尚未在項目中使用間隔組件,可是我期待可使用它們的用例。
有可能有動態的邊距嗎?例如,根據視口寬度設置具備最小值和最大值的空白。答案是確定的!咱們能夠。最近,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
。
擁有這樣的靈活性確實使人驚訝,而且爲咱們提供了構建更多動態和靈活佈局的許多可能性。
文章首發《前端外文精選》微信公衆號
繼續閱讀其餘高贊文章