CSS中的间距,前端开发中各类设置间距的优势缺点及实例

默认文件1588564373403.png

来源: https://ishadeed.com,做者:Ahmad Shadeed
翻译:公众号《前端外文精选》

若是两个或多个元素很接近,那么用户就会认为它们以某种方式属于彼此。当对多个设计元素进行分组时,用户能够根据它们之间的空间大小来决定它们之间的关系。没有间距,用户将很难浏览页面并知道哪些内容相关而哪些内容无关。javascript

在本文中,我将介绍有关CSS中的间距,实现此间距的不一样方法以及什么时候使用 padding 或 margin 所需的全部知识。css

间距类型

CSS中的间距有两种类型,一种在元素外部,另外一种在元素内部。对于本文,我将其称为outerinner。假设咱们有一个元素,它内部的间距是inner,外部的间距是outerhtml

在CSS中,间距能够以下:前端

.element {
  padding: 1rem;
  margin-bottom: 1rem;
}

我使用 padding 来填充内部间距,使用 margin 来填充外部间距。很简单,不是吗?可是,当处理具备许多细节和子元素的组件时,这会变得愈来愈复杂。java

margin 外部间距

它用于增长元素之间的间距。例如,在上一个示例中,我添加了 margin-bottom:1rem 在两个堆叠的元素之间添加垂直间距。web

因为能够沿四个不一样的方向(top、right、 bottom、left)添加margin,所以在深刻研究示例和用例以前,必定要阐明一些基本概念,这一点很重要。面试

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 添加到父元素。

负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

拥有这样的灵活性确实使人惊讶,而且为咱们提供了构建更多动态和灵活布局的许多可能性。


文章首发《前端外文精选》微信公众号

subscribe2.png

继续阅读其余高赞文章


相关文章
相关标签/搜索