[译] CSS 负边距的行为表现

2,825 阅读7分钟

原文链接:Negative margins in CSS,by PPK(有删改)

我正在写一本技术书,里面《盒子模型》一章要对负边距做阐述。出乎我意料的是,网上并没有关于负边距(negative margin)系统性介绍。所以,我只能自己试了,以下是我写的关于负边距一节的初稿。

最新规范 中仅提到“Negative values for margin properties are allowed, but there may be implementation-specific limits.”,描述得很模糊,没多大帮助。MDN 上也对此静默Rachel Andrew 的长文中 也没有提及。

有点奇怪,负边距是个非常古老的功能了,我记得第一次使用要回溯到 1998 年了(或是使用的 position: relative?不记得了)。

不管如何,本篇文章是有史以来第一次系统性阐述负边距在简单场景下的行为表现。

CSS 中的负边距

margin 是可以设置为负值的,这会帮你实现靠近顶部/左边相邻元素的效果,或者实现靠近底部/右边相邻元素的效果。

先介绍下我们的测试元素:一个简单的包含三个段落的容器元素。注意,段落设置了固定宽度 250px

<div class="test-container">
 <p>First paragraph with a bit of text in it to provide some content.</p>
 <p>Second paragraph with a bit of text in it to provide some content.</p>
 <p>Third paragraph with a bit of text in it to provide some content.</p>
</div>

<style>
* { box-sizing: border-box; }
.container {
  border5px double;
  width300px;
  padding0 10px;
}

.container p {
 border1px solid;
  width250px;
}
</style>

效果:

image.png
image.png

负边距 margin-top/bottom

先给第一段文本 margin-bottom: -15px,结果第二段文本的经浏览器重新计算,向上提升了 15px

image.png 第二段文本作为邻居紧跟在第一段文本后面,第二段文本和第三段文本之间的间距没有变化,整体依旧是垂直布局。

这个技巧比较适合用来微调位置,如果一个元素想要轻微的盖住前面一个元素的话,可以使用它。

现在恢复布局,给第二段文本 margin-top: -15px 看看效果。

image.png
image.png

可以看见,跟在第一段文本使用 margin-bottom: -15px 的效果一样。第二段文本在此被向上提升了 15px。通过在浏览器控制台查看,第一段文本的 margin-bottom 仍是默认的 1rem

边距合并(Margin collapsing)

边距塌陷行为在负边距上的行为是不同的。(至少)可以在 CSS2.1 找到 阐述 的地方:

In the case of negative margins, the maximum of the absolute values of the negative adjoining margins is deducted from the maximum of the positive adjoining margins. If there are no positive margins, the maximum of the absolute values of the adjoining margins is deducted from zero.

针对负边距场景:如果相邻两元素中一个是正边距,一个负边距,则 相邻间距(adjoining margin)= 正边距 - 负边距绝对值(结果两元素相交和相离,取决于谁的绝对值更大);如果相邻两元素中没有正边距,则 相邻边距 = 0 - 边距 1 绝对值 - 边距 2 绝对值(结果两元素相交)。

对正边距来说,规则是这样的:浏览器会比较第一段文本的 margin-bottom 和第二段文本的 margin-top,谁的值大,最终间距就是谁,以 margin-bottom: 16px 和 margin-top: 4px 为例,那么最终的间距为 16px;而对存在负边距的场景就不是这样了,像上面一个是 margin-bottom: 1em(假设是 16px),一个是 margin-bottom: -15px,那么按照规则,最终的间距是 16px - 15px,得 1px,因为是正值,所以表示两者相离 1px 的距离。

可以看见,我们可以使用负边距达到两元素相互靠近的布局,而不会受到边距合并的影响。

到这里,算是介绍完负边距 margin-top/bottom 的情况了。

负边距 margin-left/right

负边距 margin-left/right 的工作方式与 margin-top/left 一样,元素还是有一个固定宽度。下面分别给第一和第二个文本段落设置 margin-left: -10px 和 margin-right: -10px

image.png
image.png

可以看见,第一个段落向左偏移了 10px,宽度没有变化,同时右边缘也向左移动了 10px

第二个段落的负 margin-right 值没有起作用。因为 margin-right 负值影响的是第二个段落右面的元素,当前第二个段落右边是没有元素的,因此看不到效果。

为了展示 margin-right 负值效果,需要将段落元素设置成浮动的,这样就有右边的相邻元素了。

image.png 现在在段落上设置负边距。

image.png
image.png

可以看见,因为第一个段落设置了 margin-right: -10px,导致第二个段落向左偏移 10px。这跟之前看到的 margin-bottom 负值的效果是一样的。

同时,第二个段落设置了 margin-top: -10px,于是向上偏移了 10px。第三个元素设置了 margin-bottom: -10px,但没有效果,是因为底部没有元素。

译注:这里说的不准确。margin-bottom: -10px 产生了影响,效果没有出来不只是因为底部没有元素——我们将第一个元素删除,就能看到父元素高度塌陷了,塌陷的高度正好等于第三个段落元素的负边距绝对值,即 10px(如下图)。而之前没有塌陷的原因是因为第一个元素的高度撑开了父元素,导致父元素高度无法塌陷。

GIF.gif
GIF.gif

需要注意的是,边距合并只适用于 margin-topmargin-bottom 属性,不对 margin-leftmargin-right 起作用,所以不用担心这里的左右边距的合并问题。

如果,我们只是给第二个段落设置 margin-left: -10px,能看到同样的效果。

image.png 可以看见,在元素固宽情况下,margin-leftmargin-right 负值的行为表现跟  margin-topmargin-bottom 负值的行为表现是一样的。

width: auto 和 margin-right 负值

现在不为段落设置固定宽度,而是让它们使用默认的 width: auto 设置观察 margin-right 的负值行为表现。默认情况下,width: auto 段落元素默认会充满在父元素宽度,同时受限于父元素的 padding

现在分别给第一和第二个段落设置 margin-left: -10px 和 margin-right: -10px,第三个元素同时设置 margin-left: -10pxmargin-right: -10px 查看效果。注意,为了方便对照,这里加入了一个参考元素(Reference paragraph):

image.png
image.png

观察发现:第一个段落向左偏移了 10px,宽度增加了,右边缘未受到影响,位置未变;第二个段落向右偏移了 10px,宽度增加了,左边缘未受到影响,位置未变。这种情况,只在 width: auto 下发生,这与固定宽度的元素表现是不一样的。

第三个段落的左右两端都使用负边距值,导致左右都向外延伸了 10px 的距离,正好抵消了容器元素左右 10pxpadding。这是负边距最常用的应用场景——为了让内容与容器间保持一定的留白间隙,容器设置了 padding,但是内容里的一个标题需要延伸到整个容器的宽度展示(不畏外部 padding 值),这就到使用负边距的时候了。

image.png 这里贴出了上面结构的样式(容器元素设置了 padding: 10px)。

h5 {
 margin-left: -10px;
 margin-right: -10px;
 padding-left10px;
 margin-top0;
 margin-bottom0;
 background-color: grey;
 color: white;
 /* no width, so defaults to width: auto */ 
}

再一次要说明的是,这只在标题元素 width: auto 的情况下才能生效,不过这已经覆盖 99% 的实际使用场景了。

以上即是对负边距在简单场景下行为表现的阐述,以此为基础,我们就可以进一步研究负边距在弹性布局和网格布局中的行为表现了。

(正文完)


广告时间(长期有效)

我有一位好朋友开了一间猫舍,在此帮她宣传一下。现在猫舍里养的都是布偶猫。如果你也是个爱猫人士并且有需要的话,不妨扫一扫她的【闲鱼】二维码。不买也不要紧,看看也行。

(完)