关于 CSS 边距你需要知道的一切

已发表: 2022-03-10
快速总结 ↬ CSS 中的边距乍一看似乎很简单。 应用于一个元素,它在元素周围形成一个空间,将其他元素推开。 但是,利润比您想象的要多。

当我们学习 CSS 时,我们大多数人学到的第一件事就是 CSS 中盒子的各个部分的细节,被描述为 CSS 盒子模型。 盒子模型中的一个元素是边距,一个盒子周围的透明区域,它将其他元素推离盒子内容。 在 CSS1 中, margin-topmargin-rightmargin-bottommargin-left属性以及同时设置所有四个属性的简写margin被描述了。

边距似乎是一件相当简单的事情,但是,在本文中,我们将看看一些在使用边距方面会绊倒人们的事情。 特别是,我们将研究边距如何相互作用,以及边距折叠实际上是如何工作的。

CSS 盒子模型

与所有关于 CSS 盒子模型部分的文章一样,我们应该定义我们所说的意思,以及模型是如何通过 CSS 版本来阐明的。 盒子模型是指盒子的各个部分——内容、内边距、边框和边距——是如何布置和相互作用的。 在 CSS1 中,Box Model 使用下图所示的 ASCII 艺术图进行了详细说明。

盒子模型的ascii艺术绘图
CSS1 中 CSS 盒模型的描述

盒子每一边的四个边距属性和margin简写都是在 CSS1 中定义的。

CSS2.1 规范有一个插图来演示盒子模型,还定义了我们仍然用来描述各种盒子的术语。 该规范描述了content boxpadding boxborder boxmargin box ,每个都分别由内容的边缘、填充、边框和边距定义。

CSS 盒子模型示意图
CSS2中的CSS盒模型的描述

现在有一个 3 级盒子模型规范作为工作草案。 该规范引用 CSS2 来定义盒子模型和边距,因此我们将在本文的大部分内容中使用 CSS2 定义。

跳跃后更多! 继续往下看↓

边距崩溃

CSS1 规范定义了边距,也定义了垂直边距折叠。 从那时起,这种崩溃行为一直是与利润相关的挫败感的根源。 如果您考虑到在早期,CSS 被用作文档格式语言,那么边距折叠是有道理的。 边距折叠意味着当具有下边距的标题后面跟着具有上边距的段落时,这些项目之间不会有很大的差距。

当边距折叠时,它们将合并,以便两个元素之间的空间成为两个边距中较大的一个。 较小的边距基本上在较大的边距内结束。

保证金在以下情况下崩溃:

  • 相邻的兄弟姐妹
  • 完全空的盒子
  • 父元素和第一个或最后一个子元素

让我们依次看看这些场景中的每一个,然后再看看在这些场景中防止边距崩溃的因素。

相邻的兄弟姐妹

我对边距折叠的最初描述是展示相邻兄弟之间的边距是如何折叠的。 除了下面提到的情况,如果你有两个元素在正常流程中一个接一个地显示,第一个元素的下边距将与下一个元素的上边距一起折叠。

在下面的 CodePen 示例中,有三个div元素。 第一个具有 50 像素的顶部和底部边距。 第二个的顶部和底部边距为 20 像素。 第三个的顶部和底部边距为 3em。 前两个元素之间的边距为 50 像素,因为较小的上边距与较大的下边距相结合。 3em 中后两个元素之间的边距,因为 3em 大于第二个元素底部的 20 个像素。

请参阅 Rachel Andrew 的 Pen [边距:相邻兄弟姐妹](https://codepen.io/rachelandrew/pen/OevMPo)。

请参阅笔边距:Rachel Andrew 的相邻兄弟姐妹。

完全空的盒子

如果一个盒子是空的,那么它的顶部和底部边距可能会相互折叠。 在下面的 CodePen 示例中,具有 empty 类的元素的顶部和底部边距为 50 像素,但是,第一项和第三项之间的间距不是 100 像素,而是 50。这是由于两个边距折叠造成的。 向该框添加任何内容(甚至填充)将导致使用顶部和底部边距并且不会折叠。

请参阅 Rachel Andrew 的 Pen [边距:空框](https://codepen.io/rachelandrew/pen/JQLGMr)。

请参阅笔边距:Rachel Andrew 的空盒子。

父元素和第一个或最后一个子元素

这是最常引起人们注意的边缘崩溃场景,因为它看起来并不特别直观。 在下面的 CodePen 中,我有一个带有类包装器的div ,并且我为该div提供了一个红色的outline ,以便您可以看到它在哪里。 三个子元素都有 50 像素的边距。 但是,第一个和最后一个项目与包装的边缘齐平; 元素和包装器之间没有 50 像素的边距。

请参阅 Rachel Andrew 的 Pen [边距:第一个和最后一个孩子的边距](https://codepen.io/rachelandrew/pen/BgrKGp)。

请参阅笔边距:Rachel Andrew 的第一个和最后一个孩子的边距。

这是因为子级的边距与父级的任何边距一起折叠,因此最终位于父级的外部。 如果您使用 DevTools 检查第一个孩子,您可以看到这一点。 突出显示的黄色区域是边距。

带有黄色突出显示边距的项目显示在父项之外
DepvTools 可以帮助您查看保证金的最终结果

仅块边距折叠

最后一个示例还强调了有关边距折叠的一些内容。 在 CSS2 中,只有垂直边距被指定为折叠 - 如果您处于水平书写模式,则即元素的顶部和底部边距。 所以上面的左右边距不会折叠并最终在包装器之外。

注意值得记住的是,边距仅在块方向上折叠,例如在段落之间。

防止保证金崩溃的事情

如果项目具有绝对定位或浮动,则边距永远不会折叠。 但是,假设您遇到了上述边距崩溃的地方之一,您如何阻止这些边距崩溃?

停止崩溃的第一件事是有问题的元素之间存在某些东西的情况。

例如,一个完全没有内容的盒子不会折叠它的顶部和底部边距,如果它有一个边框,或者应用了填充。 在下面的示例中,我在框中添加了 1px 的填充。 现在在框的上方和下方都有 50 像素的边距。

请参阅 Rachel Andrew 的 Pen [边距:带填充的空框不会折叠](https://codepen.io/rachelandrew/pen/gNeMpg)。

请参阅 Pen Margins:带有填充的空框不会折叠 Rachel Andrew。

这背后有逻辑,如果盒子完全是空的,没有边框或填充,它本质上是不可见的。 它可能是您的 CMS 放入标记中的空段落元素。 如果您的 CMS 添加了多余的段落元素,您可能不希望它们在其他段落之间造成较大的间隙,因为它们的边距得到尊重。 在盒子里添加任何东西,你就会得到这些空白。

在第一个或最后一个通过父级折叠的子级上的边距可以看到类似的行为。 如果我们给父级添加边框,子级的边距会留在里面。

请参阅 Rachel Andrew 的 Pen [边距:如果父级有边框,则第一个和最后一个子级的边距不会折叠](https://codepen.io/rachelandrew/pen/vqRKKX)。

请参阅 Pen Margins:如果父级有 Rachel Andrew 的边框,则第一个和最后一个子级的边距不会折叠。

再一次,这种行为有一些逻辑。 如果出于语义目的而使用包装元素而不会在视觉上显示,那么您可能不希望它们在显示中引入大的间隙。 当网络主要是文本时,这很有意义。 当我们使用元素来布局设计时,它作为行为不太有用。

创建块格式化上下文

新的块格式上下文 (BFC) 还将防止边距通过包含元素折叠。 如果我们再看一下第一个和最后一个孩子的例子,它们的边距在包装器之外,并给包装器display: flow-root ,从而创建一个新的 BFC,边距留在里面。

请参阅 Rachel Andrew 的 Pen [Margins: a new Block Formatting Context contains margins](https://codepen.io/rachelandrew/pen/VJXjEp)。

请参阅钢笔边距:一个新的块格式化上下文包含 Rachel Andrew 的边距。

要了解有关display: flow-root更多信息,请阅读我的文章“了解 CSS 布局和块格式化上下文”。 将overflow属性的值更改为auto将产生相同的效果,因为这也会创建一个新的 BFC,尽管它也可能会创建在某些情况下您不想要的滚动条。

Flex 和网格容器

Flex 和 Grid 容器为它们的子容器建立 Flex 和 Grid格式化上下文,因此它们具有不同的行为来阻止布局。 这些差异之一是边距不会崩溃:

“一个 flex 容器为其内容建立了一个新的 flex 格式化上下文。 这与建立块格式化上下文相同,只是使用 flex 布局而不是块布局。 例如,浮动不会侵入 flex 容器,并且 flex 容器的边距不会与其内容的边距一起折叠。”

— 弹性盒 1 级

如果我们采用上面的示例并将包装器制作成一个 flex 容器,使用flex-direction: column显示项目,您可以看到边距现在包含在包装器中。 此外,相邻弹性项目之间的边距不会相互折叠,所以我们最终在弹性项目之间有 100 像素,项目顶部和底部的总和为 50 像素。

请参阅 Rachel Andrew 的 Pen [边距:弹性项目的边距不会折叠](https://codepen.io/rachelandrew/pen/mZxreL)。

请参阅 Pen Margins:Rachel Andrew 不会折叠弹性项目的边距。

您网站的保证金策略

由于边距崩溃,最好采用一致的方式来处理站点中的边距。 最简单的做法是只在元素的顶部底部定义边距。 这样,您不应该经常遇到边距折叠问题,因为有边距的一侧总是与没有边距的一侧相邻。

注意Harry Roberts 有一篇很棒的帖子详细说明了为什么只在一个方向上设置边距是一个好主意,而不仅仅是因为解决了折叠边距问题。

此解决方案无法解决您可能遇到的问题,即孩子的边距通过其父级崩溃。 这个特定问题往往不太常见,了解它发生的原因可以帮助您找到解决方案。 一个理想的解决方案是给需要它的组件display: flow-root ,作为旧浏览器的后备,您可以使用overflow来创建 BFC,将父级转换为 flex 容器,甚至引入单个像素的填充。 不要忘记您可以使用功能查询来检测对display: flow-root支持,因此只有旧浏览器才能获得不太理想的修复。

大多数时候,我发现知道为什么利润率会下降(或没有下降)是关键。 然后,您可以根据具体情况确定如何处理它。 无论您选择什么,请务必与您的团队分享该信息。 很多时候margin collapsing有点神秘,所以采取措施应对它的原因可能并不明显! 代码中的注释对您有很大帮助——您甚至可以链接到本文并帮助分享页边距折叠知识。

我想我会用其他一些与边距相关的信息来总结这篇文章。

百分比保证金

当你在 CSS 中使用百分比时,它必须是某物的百分比。 使用百分比设置的边距(和填充)将始终是父级的内联大小(水平书写模式下的宽度)的百分比。 这意味着当使用百分比时,您将在元素周围有相同大小的填充。

在下面的 CodePen 示例中,我有一个 200 像素宽的包装器,里面是一个具有 10% 边距的框,边距是 20 像素,即 200 的 10%。

请参阅 Rachel Andrew 的 Pen [边距:百分比边距](https://codepen.io/rachelandrew/pen/orqzrP)。

请参阅笔边距:Rachel Andrew 的百分比边距。

流动相关世界中的边距

我们在整篇文章中一直在讨论垂直边距,然而,现代 CSS 倾向于以相对流动的方式而不是物理方式来考虑事物。 因此,当我们谈论垂直边距时,我们实际上是在谈论块维度中的边距。 如果我们处于水平书写模式,这些边距将是顶部和底部,但在从左到右书写的垂直书写模式中,这些边距将是左右。

一旦使用逻辑的、流动的相对方向,谈论块开始和块结束就变得更容易了,而不是顶部和底部。 为了使这更容易,CSS 引入了逻辑属性和值规范。 这将流相关属性映射到物理属性上。

对于边距,这为我们提供了以下映射(如果我们使用英语或任何其他具有从左到右文本方向的水平书写模式)。

  • margin-top = margin-block-start
  • margin-right = margin-inline-end
  • margin-bottom = margin-block-end
  • margin-left = margin-inline-start

我们还有两个新的速记,允许同时设置两个块或同时设置两个块。

  • margin-block
  • margin-inline

在下一个 CodePen 示例中,我使用了这些流相关关键字,然后更改了框的书写模式,您可以看到边距如何跟随文本方向,而不是与物理上、右、下和左绑定。

请参阅 Rachel Andrew 的 Pen [Margins: flow relative margins](https://codepen.io/rachelandrew/pen/BgrQRj)。

请参阅钢笔边距:Rachel Andrew 的流动相对边距。

您可以在 MDN 上阅读有关逻辑属性和值的更多信息,或者在 Smashing Magazine 上的文章“了解逻辑属性和值”中阅读。

总结

您现在知道关于利润的大部分知识了! 简而言之:

  • 保证金崩溃是一回事。 了解它为什么会发生以及何时不会发生将帮助您解决它可能导致的任何问题。
  • 在一个方向上设置边距只能解决许多与边距相关的难题。
  • 与 CSS 中的任何内容一样,与您的团队分享您所做的决定,并评论您的代码。
  • 随着网络朝着与书写模式无关的方向发展,考虑块和内联尺寸而不是物理顶部、右侧、底部和左侧将有助于您。