https://ishadeed.com/article/spacing-in-css/
如果两个或更多元素接近,则用户将假设它们以某种方式属于彼此。当对多个元素进行分组设计时,用户可以通过它们之间的空间量来决定它们之间的关系。如果没有间距,用户将很难分清页面上哪些项目内容相关,哪些项目内容不相关。
因此,在本文中,我将分享关于 CSS 中的间距、实现该间距的不同方法以及何时使用填充或边距的所有信息。
现在,让我们开始吧。
间距类型
CSS 中的间距有两种类型,一种在元素外,另一种在元素内。对于本文,我将它们称为外层和内层。假设我们有一个元素,它里面的间距是内间距,它外面的间距是外间距。
在 CSS 中,可以按如下方式设置间距:
代码语言:javascript复制.element {
padding: 1rem;
margin-bottom: 1rem;
}
我为内部间距使用了padding,为外部使用了margin。 很简单,不是吗? 但是,在处理具有大量细节和子元素的组件时,这可能会变得越来越复杂。
Margin- 外部间距
它用于在一个元素和另一个元素之间添加间距。 例如,在前面的示例中,我添加了 margin-bottom: 1rem 以在两个堆叠元素之间添加垂直间距。
由于可以在四个不同的方向(上、右、下、左)添加边距,因此在深入示例和用例之前阐明一些基本概念非常重要。
边距折叠
简而言之,当两个垂直元素有一个边距,并且其中一个的边距大于另一个时,就会发生边距折叠。 在这种情况下,将使用较大的边距,而忽略另一个边距。
在上面的模型中,一个元素具有底部边缘,而另一个元素具有顶部边缘。 具有较大边距的元素获胜。
为避免此类问题,建议根据本文使用单向边距。 更重要的是,CSS Tricks 在 margin-bottom 和 margin-top 之间进行了投票。 61% 的选民更喜欢边缘底部而不是边缘顶部。
请参阅下面的问题是如何解决的:
CSS:
代码语言:javascript复制.element:not(:last-child) {
margin-bottom: 1rem;
}
使用 :not CSS 选择器,你可以轻松地删除最后一个子元素的边距以避免不必要的间距。
演示地址:https://codepen.io/shadeed/pen/BaoNYvL/3c1483c246958ddf6af808c28b8981d8?editors=1100
另一个与边距折叠相关的示例是子级和父级,让我们假设以下内容:
HTML:
代码语言:javascript复制<div class="parent">
<div class="child">I'm the child element</div>
</div>
CSS:
代码语言:javascript复制.parent {
margin: 50px auto 0 auto;
width: 400px;
height: 120px;
}
.child {
margin: 50px 0;
}
请注意,子元素粘在其父元素的顶部, 那是因为它的边距被折叠了。 根据 W3C,以下是针对该问题的一些解决方案:
- 给父元素添加边框
- 将子元素显示更改为 inline-block
更直接的解决方案是将 padding-top 添加到父元素。
负边距
它可以与四个方向的边距一起使用,在某些用例中非常有用。 让我们假设以下内容:
父级有 padding: 1rem,这导致子级从顶部、左侧和右侧偏移。 但是,子元素应该紧贴其父元素的边缘。 好吧,负利润来拯救!
CSS:
代码语言:javascript复制.parent {
padding: 1rem;
}
.child {
margin-left: -1rem;
margin-right: -1rem;
margin-top: -1rem;
}
演示地址:https://codepen.io/shadeed/pen/XWmbORV/dc3e136995772723a868fe440ff7a6aa?editors=0100
如果您有兴趣深入挖掘负边距,我推荐 Peter-Paul Koch 的这篇文章:https://www.quirksmode.org/blog/archives/2020/02/negative_margin.html。
填充 - 内部间距
正如我之前提到的,填充在元素内部添加了内部间距。它的目标是可以根据使用的情况而有所不同。
例如,它可以用来增加链接周围的间距,这将导致链接的可点击区域更大。
填充不起作用
值得一提的是,垂直填充不适用于具有 display: inline 的元素,例如 <span> 或 <a>。 如果添加了填充,它不会影响元素并且填充将覆盖其他内联元素。
这只是一个友好的提醒,应该为内联元素更改显示属性。
CSS:
代码语言:javascript复制.element span {
display: inline-block;
padding-top: 1rem;
padding-bottom: 1rem;
}
CSS 网格间隙
在 CSS 网格中,可以使用 grid-gap 属性轻松地在列和行之间添加间距。 它是行间距和列间距的简写。
CSS:
代码语言:javascript复制.element {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 16px; /* Adds gap of 16px for both rows and columns */
}
间隙的速记属性可以如下使用:
代码语言:javascript复制.element {
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 24px;
grid-column-gap: 16px;
}
CSS Flexbox 差距
gap 是一个提议的属性,将用于 CSS 网格和 flexbox。 在撰写本文时,它仅在 Firefox 中受支持的缺点。
代码语言:javascript复制.element {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
除此之外,不可能将它与 CSS @supports 一起使用来检测它是否受支持并在此基础上进行增强。 如果您喜欢它,请投票以帮助将其引入 Chrome。
CSS 定位
它可能不是分隔元素的直接方式,但它在某些设计案例中发挥作用。 例如,一个绝对定位的元素,需要从其父元素的左边缘和上边缘定位 16px。
考虑以下示例,一张卡片,其图标应与其父级的左上角间隔开。 在这种情况下,将使用以下 CSS:
代码语言:javascript复制.category {
position: absolute;
left: 16px;
top: 16px;
}
用例和实际例子
在本节中,我们将了解在从事 CSS 项目的日常工作中会遇到的不同用例。
标题组件
在这种情况下,标题具有徽标、导航和用户配置文件。 你能猜出在 CSS 中应该如何设置间距吗? 好吧,让我为你添加一个骨架模型。
HTML:
代码语言:javascript复制<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>
标题在左侧和右侧有填充,这样做的目的是防止内容粘在边缘。
代码语言:javascript复制.c-header {
padding-left: 16px;
padding-right: 16px;
}
对于导航来说,每个链接的垂直和水平边都应该有足够的填充,所以它的可点击区域可以很大,这将增强可访问性。
代码语言:javascript复制.c-nav a {
display: block;
padding: 16px 8px;
}
对于每个项目之间的间距,你可以使用边距或将 <li> 的显示更改为 inline-block。 内联块元素在其兄弟元素之间添加了一点空间,因为它将元素视为字符。
代码语言:javascript复制.c-nav li {
/* This will create the spacing you saw in the skeleton */
display: inline-block;
}
最后,头像和用户名在其左侧有一个边距。
代码语言:javascript复制.c-user img,
.c-user span {
margin-left: 10px;
}
请注意,如果你正在构建多语言网站,建议使用 CSS 逻辑属性,如下所示。
代码语言:javascript复制.c-user img,
.c-user span {
margin-inline-start: 1rem;
}
请注意,分隔符周围的间距现在相等,原因是导航项没有特定的宽度,而是有填充。 因此,导航项的宽度取决于它们的内容。 以下是解决方案:
- 设置导航项的最小宽度
- 增加水平填充
- 在分隔符的左侧添加额外的边距
最简单更好的解决方案是第三种,即添加一个margin-left。
代码语言:javascript复制.c-user {
margin-left: 8px;
}
演示地址:https://codepen.io/shadeed/pen/oNjjaVm/20f8173f6827bf2e0bc7499798ab2ffe?editors=0100
网格系统中的间距 - Flexbox
网格是间距最常用的情况之一,考虑以下示例:
间距应该在列和行之间,考虑以下 HTML 标记:
代码语言:javascript复制<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,我的卡片组件将位于其中。
代码语言:javascript复制.grid--4 {
display: flex;
flex-wrap: wrap;
}
.grid__item {
flex-basis: 25%;
margin-bottom: 16px;
}
使用上面的 CSS,每行将有四张卡片,这是在它们之间添加空间的一种可能解决方案:
代码语言:javascript复制.grid__item {
flex-basis: calc(25% - 10px);
margin-left: 10px;
margin-bottom: 16px;
}
通过使用 CSS calc() 函数,从 flex-basis 中扣除边距。 如你所见,这个解决方案并不容易。 我更喜欢的是以下内容:
- 向网格项添加 padding-left
- 将具有相同 padding-left 值的负 margin-left 添加到网格父级。
多年前我从 CSS Wizardy 中学到了上面的解决方案(我忘记了文章标题,如果你知道请告诉我)。
代码语言:javascript复制.grid--4 {
display: flex;
flex-wrap: wrap;
margin-left: -10px;
}
.grid__item {
flex-basis: 25%;
padding-left: 10px;
margin-bottom: 16px;
}
我使用负边距的原因是因为第一张卡有 padding-left 而实际上它是不需要的。 所以它会将包装器推到左边并取消不需要的空间。
演示地址:https://codepen.io/shadeed/pen/gOaPwEj/b4abf0f83804991925de43367562d93f?editors=1100
另一个类似的概念是向两边添加填充,然后边距为负。 以下是 Facebook 故事的示例:
代码语言:javascript复制.wrapper {
margin-left: -4px;
margin-right: -4px;
}
.story {
padding-left: 4px;
padding-right: 4px;
}
网格系统中的间距 - CSS 网格
现在,到了激动人心的部分! 使用 CSS 网格,你可以使用 grid-gap 轻松添加间距。 此外,你不需要关心网格项目的宽度或底部边距。 CSS Grid 为你做一切!
代码语言:javascript复制.grid--4 {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 1rem;
}
就是这样! 这不是更容易和直接吗?
按需差距
我真正喜欢 CSS 网格的地方是 grid-gap 仅在需要时才应用,考虑以下模型。
我有一个有两张卡片的部分。 在移动设备上,我希望间距低于第一个,而在桌面上,间距将在它们之间。 如果没有 CSS 网格,就不可能有这种灵活性。
CSS:
代码语言:javascript复制.card:not(:last-child) {
margin-bottom: 16px;
}
@media (min-width: 700px) {
.card:not(:last-child) {
margin-bottom: 0;
margin-left: 1rem;
}
}
不舒服,对吧? 这个如何?
代码语言:javascript复制.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 选择器
代码语言:javascript复制.element:not(:last-child) {
margin-bottom: 16px;
}
解决方案 2 - 相邻兄弟组合器
代码语言:javascript复制.element .element {
margin-top: 16px;
}
虽然,解决方案 #1 很诱人,但它有以下缺点:
- 它会导致 CSS 特异性问题。 在使用 :not 选择器之前无法覆盖它。
- 如果设计有多于一列,它将不起作用,见下图。
关于解决方案 #2,它没有 CSS 特异性问题。 但是,它仅适用于一个列堆栈。
更好的解决方案是通过向父元素添加负边距来取消不需要的间距。
代码语言:javascript复制.wrapper {
margin-bottom: -16px;
}
这就是发生的事情, 它将元素推到底部,其值等于底部间距。 注意不要超过边距值,因为它会重叠其兄弟元素。
卡片组件
哦,如果我想详细了解卡片组件间距,我可能会写一本书。 我将突出显示一个通用模式,看看应该如何应用间距。
(对不起,如果你饿了)
你能想到这张卡的间距会在哪里使用吗? 见下图。
HTML:
代码语言:javascript复制<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>
CSS:
代码语言:javascript复制.card__content {
padding: 10px;
}
上面的填充将为其中的所有子元素添加偏移量。 然后,我将添加所有边距。
CSS:
代码语言:javascript复制.card__title,
.card__author,
.card__rating {
margin-bottom: 10px;
}
对于评分和卡片元数据之间的分隔线,我将其添加为边框。
代码语言:javascript复制.card__meta {
padding-top: 10px;
border-top: 1px solid #e9e9e9;
}
哎呀! 由于应用于父元素 .card__content 的填充,边框不会粘在边缘上。
是的,你猜对了! 负边距是解决办法。
代码语言:javascript复制.card__meta {
padding-top: 10px;
border-top: 1px solid #e9e9e9;
margin: 0 -10px;
}
哎呀,又来了! 出了点问题。 内容卡在边缘!
为了解决这个问题,应该从左右边缘对内容进行填充(哦,看起来填充是一个新词)。
CSS:
代码语言:javascript复制.card__meta {
padding: 10px 10px 0 10px;
border-top: 1px solid #e9e9e9;
margin: 0 -10px;
}
演示地址:https://codepen.io/shadeed/pen/RwWaPbx/a95840a4d64d51beef15b01373e894c6?editors=1100
Article Content
我相信这是一个非常非常常见的用例。 由于文章内容来自 CMS(内容管理系统)或从无法为元素添加类的降价文件自动生成。
考虑以下包含标题、段落和图像的混合示例。
HTML
代码语言:javascript复制<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 借了一些样式。
代码语言:javascript复制h1,
h2,
h3,
h4,
h5 {
margin: 2.75rem 0 1.05rem;
}
h1 {
margin-top: 0;
}
img {
margin-bottom: 0.5rem;
}
如果 <p> 后跟一个标题,例如“间距类型”,则 <p> 的底部边距将被忽略。 你猜对了,那是因为边距折叠失效了。
演示地址:https://codepen.io/shadeed/pen/eYpZVzN/f645a0a31d76d0d294d498c6b7345175?editors=0100
以防万一的Margin
我喜欢称其为“以防万一”margin,因为这就是字面意思。 考虑下面的模型:
当它们彼此靠近时,这些元素看起来并不好,我用 flexbox 构建它们,这种技术被称为“Alignment Shifting Wrapping”,我从 CSS Tricks 中了解到它的名字。
代码语言:javascript复制.element {
display: flex;
flex-wrap: wrap;
}
当视口尺寸较小时,它们确实会在新行中结束, 见下图:
需要解决的是in-between设计状态,两个item仍然相邻,但它们之间的间距为零。 在这种情况下,我更喜欢为元素添加一个margin-right,这样可以防止它们相互接触,这将使flex-wrap 工作得更快。
CSS writing-mode
根据 Mozilla 开发者网络 (MDN):
CSS writing-mode属性设置文本行是水平还是垂直布局,以及块前进的方向。
你有没有想过当margin与具有不同书写模式的元素一起使用时应该如何表现? 考虑以下示例。
代码语言:javascript复制.wrapper {
/* To make the title and the recipe in the same line */
display: flex;
}
.title {
writing-mode: vertical-lr;
margin-right: 16px;
}
标题旋转了 90 度,它和图像之间应该有一个空间。 事实证明,边距在写作模式的基础上工作得很好。
演示地址:https://codepen.io/shadeed/pen/rNOLjXK/8f39303d78dc780c0a22bafd682824bb?editors=1100
我认为这对于用例来说已经足够了。 让我们继续讨论一些有趣的概念!
组件封装
一个大型设计系统包含如此多的组件, 直接向它们添加边距是否合乎逻辑?
考虑以下示例。
代码语言:javascript复制<button class="button">Save Changes</button>
<button class="button button-outline">Discard</button>
应该在哪里添加按钮之间的间距? 应该添加到左键还是右键? 也许你可以使用相邻的兄弟组合器,如下所示:
代码语言:javascript复制.button .button {
margin-left: 1rem;
}
情况不妙,如果只有一个按钮的情况怎么办? 或者,当它垂直堆叠时,这将如何在移动设备上工作? 很多很多的复杂性。
使用抽象组件
上述问题的一个解决方案是拥有抽象的组件,目的是托管其他组件。 正如 Max Stoiber 所说,这有点将管理边距的责任转移到父元素上,让我们以这种心态重新考虑以前的用例。
代码语言:javascript复制<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>
请注意,我添加了一个包装器元素,现在每个按钮都包装在自己的元素中。
代码语言:javascript复制.list {
display: flex;
align-items: center;
margin-left: -1rem; /* Cancels the left margin for the first element */
}
.list__item {
margin-left: 1rem;
}
就是这样! 更重要的是,在任何 JavaScript 框架中应用这些概念都相当容易。 例如:
代码语言:javascript复制<List>
<button>Save Changes</button>
<button outline>Discard</button>
</List>
你使用的 JavaScript 工具应该将每个项目包装在其自己的元素中。
间隔组件
是的,你没看错, 有人指出这篇文章讨论了避免边距并使用间隔组件而不是它们的概念。
让我们假设一个部分需要从左边算起 24px 的边距,考虑到这些限制:
Margin 不能直接用于组件,因为它是一个已经构建的设计系统。
它应该是灵活的,间距可能在 X 页面上,但不在 Y 页面上。
我在检查 Facebook 的新设计 CSS 时,首先注意到了这一点。
我们将内联样式div设置为width :16px, 它的唯一目的是在左边缘和包装器之间添加一个空间。
引用一下React 的说法:
但在现实世界中,我们确实需要在组件之外留出间距,以便将它们组合成页面和场景,这就是折叠渗入组件代码的地方:用于间隔组件的组合。
我同意。对于大型设计系统,不断为组件添加边距是不可扩展的。这最终将导致令人毛骨悚然的代码。
间隔组件的挑战
现在你已经了解了间隔组件的概念,让我们深入了解使用它们时的一些预期挑战。以下是我想到的一些问题:
- 间隔组件如何在父组件中获取其宽度或高度?它将如何在水平和垂直布局中工作?例如:堆栈内的间隔符与添加左侧空间的间隔符。
- 我们是否应该根据父级的显示类型(Flex、Grid)来设置它们的样式
让我们一一解决上述问题。
调整间隔组件
可以创建一个接受不同变化和设置的元素。我不是 JavaScript 开发人员,但我认为他们称之为 Props。考虑来自 styled-system.com 的以下内容:
我们在标题和部分之间有一个间隔。
代码语言:javascript复制<Header />
<Spacer mb={4} />
<Section />
虽然这有点不同,在标题、logo和导航之间创建自动间距的分隔符。
代码语言:javascript复制<Flex>
<Logo />
<Spacer m="auto" />
<Link>Beep</Link>
<Link>Boop</Link>
</Flex>
你可能认为通过添加 justify-content: space-between 来使用 CSS 实现这一点相当容易。 如果设计需要改变怎么办? 那么,在这种情况下,样式应该改变。
见下文,你看到哪里的灵活性了吗?
代码语言:javascript复制<Flex>
<Logo />
<Link>Beep</Link>
<Link>Boop</Link>
<Spacer m="auto" />
<Link>Boop</Link>
</Flex>
那么,在这种情况下,样式应该改变,你看到它的灵活性了吗?
对于尺寸调整部分,可以根据其父级来调整元素的尺寸。 对于上述情况,也许你可以制作一个名为 grow 的 prop,它在 CSS 中计算为 flex-grow: 1。
代码语言:javascript复制<Flex>
<Spacer grow="1" />
</Flex>
使用伪元素
我想到的另一个想法是使用伪元素来创建间隔。
代码语言:javascript复制.element:after {
content: "";
display: block;
height: 32px;
}
也许我们可以选择通过伪元素而不是单独的元素来添加分隔符? 例如:
代码语言:javascript复制<Header spacer="below" type="pseudo" length="32">
<Logo />
<Link>Home</Link>
<Link>About</Link>
<Link>Contact</Link>
</Header>
直到今天,我还没有在我的项目中使用间隔组件,但我期待着我可以使用它们的用例。
CSS 数学函数:Min()、Max()、Clamp()
是否有可能拥有动态margin? 例如,根据视口宽度设置具有最小值和最大值的边距。 答案是肯定的! 我们可以。
最近,CSS 数学函数在 Firefox 75 中得到支持,这意味着它们在所有主流浏览器中都受 CanIUse 支持。
让我们回顾一下网格用例,看看如何在其中使用动态间距。
代码语言:javascript复制.wrapper {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: min(2vmax, 32px);
}
下面是 min(2vmax, 32px) 的含义:使用等于 2vmax 的间隙,但不应超过 32px。
演示地址:https://codepen.io/shadeed/pen/XWmKXgO/24e4817219ba484a6199ea7648afe357
拥有这样的灵活性确实令人惊叹,并为我们提供了很多可能性来构建更加动态和灵活的布局。
写在最后
到这里,我跟你分享的关于CSS间距的知识技巧就要结束了,希望你通过阅读这篇文章,一次性搞定所有关于CSS间距的问题,如果一次没有弄明白,你可以多阅读几遍,同时,也通过手动写代码,自己去练习尝试一下。
如果你觉得我跟你分享的内容有用的话,请点赞我,关注我,并与你的开发者朋友一起来分享讨论它,如果还有问题,请在留言区给我留言,我会答复你提出的问题,如果我知道的话。
最后,感谢你的阅读,编程愉快!