CSS 里有一个重要的东西,叫 CSS 盒模型,它几乎能贯穿我们学习 CSS 的整个阶段,如下图所示,就是我们所说的盒子模型:

CSS Box model

(注:图片来源 MDN )

“盒模型”中最外层透明的区域就是 margin所在,它会将其他区域从盒子模型内容中推开。

margin 一共有四个属性值, margin-topmargin-rightmargin-bottommargin-left 属性,也可以设置简写属性 margin

margin 看起来比较简单,但是,它其实有自己的奥妙所在,比如我们在布局中经常遇到 margin 重叠效果等,接下来我们来看看 margin 究竟有什么神奇所在吧。

margin 重叠

margin 重叠指的是垂直重叠,重叠的意思是,当我们规定一个模块的 margin-bottom 与 紧邻着的下一个模块的 margin-top 它们会发生重叠,它们之间不会出现较大的空白。因为代码会取两个值之间较大的那个,比如 第一个模块设置margin-bottom: 50px ,第二个模块设置 margin-top: 100px 那么第一个模块的 margin-bottom 则不会起作用,起作用的则是 第二个模块的 margin-top 也就是说他们之间的空白间距是 100px ,可能会有疑问,那 50px 跑哪里去了,其实是被包含在 100px 里面了 这就是所谓的 margin 重叠。

以下情况下,margin 会发生重叠:

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

相邻的兄弟元素重叠

上述我们所描绘的情况就是相邻兄弟之间的 margin 重叠。

如下示例,有三个 div 元素。第一个 div 的顶部与底部的 margin 分别是 50px 第二个 div 的顶部与底部 margin 分别是 20px ,第三个 div 的顶部与底部 margin3em。 那么出现的重叠情况是,第一个与第二个的间距是 50px, 因为第二个元素的顶部小于第一个底部的 margin,取两者之间较大的。第二个元素与第三个元素之间的间距则是 3em ,同理 3em 大于第二个元素的 底部 20px

<div class="box1">模块一</div>
<div class="box2">模块二</div>
<div class="box3">模块三</div>
div {
  border: 1px solid #ddd;
  height: 200px;
}
.box1 {
  margin: 50px 0;
}
.box2 {
  margin: 20px 0;
}
.box3 {
  margin: 3em 0;
}

在线 demo

完全空盒子

什么是完全空盒子,就是盒子里面什么也没有就是完全的空盒子,没有内容也没有子元素,它顶部与底部的 margin 可能会相互重叠,如下示例,一个 classempty 的元素顶部与底部的 margin 分别为 50px , 但是第一个元素与第三个元素之间的间距并没有 100px ,而是 50px 。这就是空盒子造成的 margin 重叠,如果我们在空盒子里面写入内容就会阻止 margin 合并。

<div class="wrap">
    <div class="box1">模块一</div>
    <div class="box2 empty"></div>
    <div class="box3">模块三</div>
</div>
.wrap {
  border: 5px dotted #000;
}
.box1, .box3 {
  height: 100px;
  background: #f60;
}
.empty {
  margin: 50px 0;
}

在线 demo

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

一开始接触布局时,我们可能会经常碰到这样的问题,我们给子级设置一个margin-top 值,如果没有清除 BFC 的话,常常看到的错觉是这样的:

WX20190803-143924.png

从图上可以看到,第一项与最后一项与父元素的 margin 齐平,好像是子元素与父元素之间没有 20pxmargin 一样。

<div class="box">
  <div class="child"></div>
  <div class="child"></div>
</div>
.box {
  background: green;
}

.child {
  height: 100px;
  background: red;
  margin: 20px;
}

这是因为子节点上的 margin 会随着父元素上的任何一边 margin 相互重叠,从而最终位于父元素的外部,造成的错觉像是父元素向下 margin 或者向上 margin 一样。

注意:在 CSS 中,只指定了重置方向的 margin 重叠,即元素的顶部和底部的 margin 重叠。左右是不会发生重叠的。

还有一点就是,margin 只在块的方向上发生重叠,比如段落之间。

阻止 margin 发生重叠

如果一个元素是绝对定位,或者是浮动的, 它的 margin 永远不会发生重叠, 因为他已经脱离文档流了。

如果是一个完全空的盒子,给它设置一个 border 或者 padding ,那么这个空盒子的 margin 则不会发生重叠。

<div class="wrap">
  <div class="box1">模块一</div>
  <div class="box2 empty"></div>
  <div class="box3">模块三</div>
</div>
.wrap {
  border: 5px dotted #000;
}
.box1, .box3 {
  height: 100px;
  background: #f60;
}
.empty {
  margin: 50px 0;
  /* border: 1px solid transparent; */
  padding: 1px;
}

在线 demo

那么为什么加上 borderpadding 就可以了呢? 那是因为如果一个空盒子加上 border 或者 padding 后就有高度了就变成非空盒子了。如果我们在代码布局上,我们标记一个为空的段落元素,如果不想让他造成与其他模块较大的空白,这时候我们做的 margin 重叠就有了一定的意义。

对于父元素和第一个或者最后一个子元素的 margin 重叠,如果我们向父级添加 border 或者 padding 那么子级上的 margin 则会保留。

创建格式化上下文(BFC)

BFC (Block Formatting Context) 格式化上下文 ,是 Web 页面中盒模型布局的css渲染模式,是一个独立的渲染区域或者是一个隔离的独立容器。

BFC 可以阻止边距的重叠。我们可以在父级元素上添加BFC,从而可以避免与子级元素的 margin 重叠。

<div class="box">
  <div class="child"></div>
  <div class="child"></div>
  <div class="child"></div>
</div>
.box {
  background: green;
  overflow: hidden; /* 创建BFC */
  /*overflow: auto*/
  /*display: flow-root*/
}

.child {
  height: 100px;
  background: #f60;
  margin: 20px;
}

WX20190803-154431.png

display: flow-root 是 CSS3 新出来的一个属性,用来创建一个无副作用的 BFC。将 overflow 的属性的值设置为 auto 或者 hidden 也会产生同样的效果,只不过有时候这俩属性值会带来一些副作用。

布局时的 margin 策略

由于我们知道上下 margin会发生重叠,所以我们在网站布局时,会采用单独给一边加 margin 值的策略,要么是 margin-top 要么是 margin-bottom 这两个在布局时最好是一个元素上别同时存在,当然指的是相邻的元素。那如果是父子级身上的 margin 重叠的情况下,我们最好还是采用上述清除 margin 的办法,当然现代浏览器的发展以及一些布局方法的更新,我们可以采用 flex 布局 和 grid 布局 抛除掉 margin 的使用。当前具体问题还得具体分析,我相信我们做的例子多了 会有更深刻的认识。

百分比 margin

在 CSS 中使用百分比的时候,它必须是某个元素的百分比,也就是说我们必须有一个参考元素。使用百分比设置的 margin(或者padding) 始终是父元素内联大小(水平写入模式下单宽度)的百分比。

<div class="wrapper">
  <div class="box">
    I have a margin of 10%.
  </div>
</div>
 * {
   box-sizing: border-box;
 }
 .wrapper {
   border: 5px dotted black;
   width: 200px;
   height: 400px;
 }
 .box {
   background-color: rgb(55, 55, 110);
   color: white;
   padding: 20px;
   border-radius: .5em;
   margin: 10%;
 }
 body {
   font: 1.4em/1.3 "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
   margin: 2em 3em;
 }

在线 demo

后记

只是简单的介绍了下,平时遇到 margin 重叠的几种情况,以及我们该如何处理。还是那句话, CSS 博大精深,虽然容易入门但是后期的东西真的是让我们随时随地的去学习了,所以我们在平时的学习过程中,要记得多加练习,多多总结。

参考资料

关于 CSS margin,你需要知道的一切