本文样式代码采用 SCSS。 浏览器兼容性为 IE6+。

你的网页中,不可能没有使用过 margin。大多数情况下,我们采用的都是正数的 margin 值,可能有时候会用到负的 margin 值。在我们的印象中,负的 margin 值就类似于浏览器的 hack 一样,不被人接受。但是,本文要说明的就是,负的 margin 值并不是 hack,这是正常范围内的写法。

Negative values for margin properties are allowed, but there may be implementation-specific limits. —— W3C

根据 W3C,margin 是能够接受负值的,只是在具体实现上有一些区别。

那么,设置 margin 为负值究竟会是什么样的效果呢?

与设置正值不同,margin 设置负值需要根据设置的方向以及元素是否浮动以及其定位方式来判断最终的行为。

所以,具体行为按照以下几种情况说明。

第一种情况:元素没有设置浮动且没有设置定位或者 positionstatic

如果元素没有设置浮动并且没有设置定位或者 position 属性为 static 的情况下,对元素的 margin 设置负值会有以下的效果:

设置的 margin 的方向为 top 或者 left

当设置负值的 margin 的方向为 top 或者 left 的时候,元素会按照设置的方向移动相应的距离。

比如,设置 margin-left: -100px;。 那么,元素会往左移动 100px。对于设置 margin-top 也是一样的道理。

设置的 margin 的方向为 bottom 或者 right

当设置负值的 margin 的方向为 bottom 或者 right 的时候,元素本身并不会移动,元素后面的其他元素会往该元素的方向移动相应的距离,并且覆盖在该元素上面。

比如,设置 margin-right: -100px;。那么,元素本身并不会移动,后面的元素会向左移动 100px 到该元素上。对于设置 margin-bottom 也是同样的道理。

同时,在元素不指定宽度的情况下,如果设置 margin-left 或者 margin-right 为负值的话,会在元素对应的方向上增加其宽度。效果就和设置 padding-left 或者 padding-right 一样。

第二种情况:元素没有设置浮动且 positionrelative

如果元素没有设置浮动,但是设置了相对定位,设置 margin 为负值的时候,表现如下:

设置的 margin 的方向为 top 或者 left

当设置负值的 margin 的方向为 top 或者 left 的时候,元素也会按照设置的方向移动相应的距离。

设置的 margin 的方向为 bottom 或者 right

当设置 margin-bottom/left 的时候,元素本身也不会移动,元素后面的其他元素也会往该元素的方向移动相应的距离,但是,该元素会覆盖在后面的元素上面 (当然,此处说的情况肯定是后面的元素没有设置定位以及 z-index 的情况)。

第三种情况:元素没有设置浮动且 positionabsolute

如果元素没有设置浮动,但是设置了绝对定位,设置 margin 为负值的时候,表现如下:

设置的 margin 的方向为 top 或者 left

当设置负值的 margin 的方向为 top 或者 left 的时候,元素也会按照设置的方向移动相应的距离。

设置的 margin 的方向为 bottom 或者 right

由于设置绝对定位的元素已经脱离了标准文档流,所以,设置 margin-right/bottom 对后面的元素并没有影响。

第四种情况:元素设置了浮动

肯定没有既设置了浮动又设置绝对定位的情况,那样太荒唐了。 设置了浮动的元素,再设置 postion: relative; 的话,元素的行为和单独设置 float 是一样的。

对于设置了浮动的元素,设置 margin 为负值的时候,表现如下:

如果设置的 margin 的方向与浮动的方向相同,那么,元素会往对应的方向移动对应的距离。

比如:

.elem {
    float: right;
    margin-right: -100px;
}

该元素则会向右移动 100px。

如果设置 margin 的方向与浮动的方向相反,则元素本身不动,元素之前或者之后的元素会向钙元素的方向移动相应的距离。

比如:

.elem {
    float: right;
    margin-left: -100px;
}

位于该元素左边的元素则会向右移动 100px,同时覆盖在该元素上。

如果后面的元素也设置了浮动的话,我们以一个具体的例子来说明。

<div class="container">
    <div class="left"></div>
    <div class="right"></div>
</div>
.container {
    min-height: 300px;
    margin: 30px auto;
    overflow: hidden;
    border: 1px solid #000000;

    .left {
        float: left;
        width: 400px;
        height: 200px;
        margin-right: -300px;
        background: purple;
    }

    .right {
        float: left;
        width: 300px;
        height: 200px;
        background: #cccccc;
    }
}

.left.right 都设置了浮动,在 .left 上设置了 margin-right: -300px;,那么,.right 会向左移动 300px,从而覆盖在 .left 上。这种行为与没有既没有设置浮动也没有设置定位的表现类似。


到此,我们把设置负 margin 的各种情况以及在各种情况下的表现都大概了解了一遍。那么,我们真正运用到实际中会是什么样子呢。

半遮挡的标题

原谅我措辞不好,大概就是下图的效果:

![ center](https://sfault-image.b0.upaiyun.com/336/862/3368625051-58037eaeee013_articlex)

按照一般的思想,肯定是直接给 title 设置绝对定位,然后再将其调整过去。

但是,按照我们现在所说的,其实很简单就能实现这个效果。

这里只写了主要部分的代码。

<div class="title">Hey This is title!</div>
<div class="content">Hah! This is content.</div>
.title {
    position: relative;
    width: 200px;
    height: 60px;
    margin-bottom: -30px;
    margin-left: -20px;
    background: #000000;
}

.content {
    max-width: 800px;
    height: 400px;
    padding: 0 50px;
    background: yellow;
}

我们为 title 设置了两个 margin 的负值,分别是 margin-bottom: -30px;,以及 margin-left: -20px;

设置 margin-bottom 是为了让 content 向上移动,设置 margin-left 是为了让 title 向左移动一小段距离。

还有个需要注意的地方就是,需要给 title 设置 position: relative;,根据我们的第二种情况所说的,这样,才能保证 title 覆盖在 content 之上。

简单的一列定宽的两列流式布局

根据我们的最后一种情况,通过设置 margin 为负值,我们可以很容易的实现一列定宽的两列流式布局。

<div class="container">
    <div class="left"></div>
    <div class="right"></div>
</div>
.left {
    float: left;
    width: 100%;
    height: 200px;
    margin-right: -300px;
    background: purple;
}
.right {
    float: left;
    width: 300px;
    height: 200px;
    background: #cccccc;
}

唯一需要注意的地方就是设置了 100% 宽度的元素上的 margin 负值的绝对值一定要和定宽的元素的宽度相同。

两边固定,中间自适应的三列布局

这是一个很老的话题了,以前也有各种实现的方式,比如双飞翼布局,或者圣杯布局。

我们此处就以双飞翼布局来作示例。

先设置页面结构:

<div class="container">
    <div class="center"></div>
    <div class="left"></div>
    <div class="right"></div>
</div>

此处我们没有把 center 放在中间,具体原因后面会解释。

然后,我们设置这三列都浮动:

.left,
.right,
.center {
    float: left;
    height: 500px;
}

同时为他们指定宽度:

.left {
    width: 300px;
    background: #000000;
}

.right {
    width: 400px;
    background: #00FFFF;
}

.center {
    width: 100%;
    background: #93c759;
}

现在我们要让 left 在左边,相当于就是让它覆盖在 center 的上面,所以,只需要这样一句:

margin-left: -100%;

同时,要让 right 在右边,同理,这样设置:

margin-left: -400px;

注意,此处的 margin 值的绝对值与 right 的宽度值相同。

其实,这样设置,我们的三列布局就基本完成了。

那么,我们为什么要把 center 放在 left 和 right 之前呢?

这个其实涉及到元素的堆叠顺序的知识 (这里就不详细讲解了,后面有时间的话专门拿一篇文章来讲解吧),此处简单说明一下。

由于我们的三列都设置了浮动,所以,从某种意义上说,它们三个是在同一个平面的(相当于 z-index 相同),那么,这里就不能根据 CSS 来判断堆叠顺序了。所以,此处的 HTML 结构就决定了它们的堆叠顺序:所谓后来居上。

我们要让 left 在 center 之上,所以,肯定需要让 left 元素放在 center 之前。

所以,三列布局完整的 SCSS 代码如下:

.container {
    overflow: hidden;

    .left,
	.right,
	.center {
	    float: left;
	    height: 500px;
	}

	.left {
	    width: 300px;
	    margin-left: -100%;
	    background: #000000;
	}

	.right {
	    width: 400px;
	    margin-left: -400px;
	    background: #00FFFF;
	}

	.center {
	    width: 100%;
	    background: #93c759;
	}
}

References引用

[margin-properties W3C](https://www.w3.org/TR/CSS2/box.html#margin-properties)

The Definitive Guide to Using Negative Margins

双飞翼布局和圣杯布局的对比