神奇的margin负值

margin负值的原理

1.负边距在普通文档流中的作用和效果

那些没有脱离文档流的元素(指非浮动元素、非绝对定位、非固定定位的元素等),其在页面中的位置是随着文档流的变化而变化的。

如下图所示:
normal_1
负边距对这些由文档流控制的元素的作用是:会使它们在文档流中的位置发生偏移,但这种偏移不同于相对定位。
通过相对定位偏移后,它偏移前占据的空间仍然被它占据,不会让文档流的其它元素乘虚而入。
而通过负边距进行偏移的元素,它会放弃偏移前占据的空间,这样它后面文档流中的其它元素就会“流”过来填充这部分空间。
现在我们把上图中的block元素、inline元素以及inline-block元素都设一个负边距 margin:-10px;
看看会发生什么:
normal_2
上图中蓝色的block元素好像向左和向上都分别嵌入了浏览器窗口的边界里10px,然后block元素下面的文字也爬到了它身上,inline元素向左移动盖住了它前面的一个字,它后面的文字也有一部分覆盖在了它身上,inline-block的位置变化也很明显。
负的边距好像能减小元素在文档流中的尺寸一样,但事实上,它的尺寸大小并没变,只是文档流在计算元素位置的时候,会认为负边距把元素的尺寸减小了,元素的边界发生了变化,因此位置也就发生变化了。这只是打个很形象的比喻,帮助理解。要注意的是,文档流只能是后面的流向前面的,即文档流只能向左或向上流动,不能向下或向右移动。
简而言之,在普通文档流中,元素的最终边界是由margin决定的,margin为负的时候就相当于元素的边界向里收,文档流认的只是这个边界,不会管你实际的尺寸是多少,对于margin的top,right,bottom,left负值都是如此,margin的top和left负值表现为元素向上向左位移(因为上边界向下、左边界向右收了),而margin的right和bottom负值对自身的显示没有什么影响,但是它后面的元素会爬到它的身上(因为右边界边界向左、下边界向上收了)。多列等高布局可以利用这个实现

demo演示

2.左右负边距对元素宽度的影响

负边距不仅能影响元素在文档流中的位置,还能增加元素的宽度!
这个作用能实现的前提是:该元素没有设定width属性或width:auto。
来看下面的例子:

1
2
3
<div class="container">
<div class="box1">我没有设置宽度我没有设置宽度我没有设置宽度我没有设置宽度我没有设置宽度我没有设置宽度我没有设置宽度</div>
</div>
1
2
3
4
5
6
7
8
.container{
width: 500px;
margin: 100px auto;
border: 10px solid red;
}
.box1{
background: blue;
}

此时情况如下所示:

添加margin-right;-20px;后,变为下图:

添加margin-left;-20px;后,变为下图:

可以看到负的margin使得未设置宽度的元素的宽度发生了变化,超出了父元素。我们顺便来看看此时上下负margin的情况

添加margin-top;-10px;后,变为下图:

添加margin-bottom;-10px;后,变为下图:

最终的box1与container的位置关系如下图所示:

可以看到,margin-top为负值不会增加高度,会改变元素的最终边界,从而产生向上位移,此处向下收了10px从而向上移动了10px,父元素container设置的border也是10px,所以刚好遮住了;
而margin-bottom为负值不会位移,而是会改变元素的最终边界,此处向上收了10px,所以导致父元素container的高度也减少了10px,刚好设置的border也是10px,所以刚好遮住了

demo演示

3.负边距对浮动元素的影响

负边距对浮动元素的影响与负边距对文档流中元素的影响其实是差不多的。文档流中元素的位置由文档流的走向决定,浮动的元素也可以看成有一个“浮动流”存在,不过浮动流既可以向左,也可以向右。
来看下面的例子:

1
2
3
4
5
<div class="container">
<div class="box box1">box1</div>
<div class="box box2">box2</div>
<div class="box box3">box3</div>
</div>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.container{
width: 400px;
height: 200px;
margin: 50px auto;
border: 5px solid red;
}
.box{
float: left;
width: 100px;
height: 100px;
}
.box1{
background: blue;
}
.box2{
background: green;
}
.box3{
background: yellow;
}

此时情况如下所示:

给它们都设一个margin-right:-50px;,变为下图:

3个box的右边界都收了50px,后面的box叠到了前面的box上

再看缩小父元素container宽度为220px后的情况:

此时由于宽度不够box3掉下来了

我们给box3单独设一个margin-left:-80px;看看会怎么样

看~它上去了,并且盖住了box2的80px,因为box3的左边界向右收了80px,所以它占据的宽度只有20px,这样父元素的宽度够容纳box3,于是它就上去了。
现在将box3的设为margin-left:-100px;

此时box3完全盖住box2
现在将box3的设为margin-left:-200px;

此时box3完全盖住box1,box2又出现了
所以负margin会改变浮动元素的显示位置,某个元素虽然是写在了后面,但可以通过负边距让它在浏览器显示的时候是在前面的。圣杯布局双飞翼布局都是利用这个原理实现的。

demo演示

4.负边距对绝对定位元素的影响

绝对定位的元素定义的top、right、bottom、left等值是元素自身的边界到最近的已定位的祖先元素的距离,这个元素自身的边界指的就是margin定义的边界,所以,如果margin为正的时候,那它的边界是向外扩的,如果margin为负的时候,则它的边界是向里收的。利用这点,就有了经典的利用绝对定位来居中的方法:

1
<div class="absolute"></div>
1
2
3
4
5
6
7
8
9
10
.absolute{
position: absolute;
top:50%;
left:50%;
height: 200px;
width: 200px;
background-color: blue;
margin-top: -100px;
margin-left: -100px;
}

这样就实现了如下所示的水平垂直居中:

demo演示

margin负值的应用

  • 圣杯布局
  • 双飞翼布局
  • 多列等高布局
  • 水平垂直居中(上文有实现方法)
  • 去除列表最右边的边距
    我们经常会使用浮动列表展示信息,为了美观通常为每个列表之间设置一定的间距(margin-right),当父元素的宽度固定式,靠近右边边界的子元素就不应该有正向的margin-right了,否则这一行就容纳的子元素个数就会少一个,去除的方法通常是为最右端的li添加class,设置margin-right:0; ,如果这些子元素是在模板中通过循环动态输出的,那在循环的时候还得判断哪些子元素是靠近右边边界的,如果是就加上一个class。这时就会很麻烦,所以解决办法是加大子元素的父容器的宽度,可以在css里直接增大父容器宽度,外层container再overflow即可,但是利用负margin也可以实现这种效果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div class="container">
    <ul>
    <li>子元素1</li>
    <li>子元素2</li>
    <li>子元素3</li>
    <li>子元素4</li>
    <li>子元素5</li>
    <li>子元素6</li>
    </ul>
    </div>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    .container{
    width:320px;
    height:210px;
    margin: 30px auto;
    border:5px solid #000;
    /*overflow: hidden;*/
    }
    .container ul{
    margin-right: -10px;/* 相当于宽度增加10px*/
    overflow: hidden;
    zoom :1;
    /*width: 330px;*/
    }
    .container ul li{
    width:100px;
    height:100px;
    float:left;
    margin-right:10px;
    margin-bottom:10px;
    background:#06c;
    }


    demo演示

  • 去除列表最后一个li元素的border-bottom
    列表中我们经常会添加border-bottom值,最后一个li的border-bottom往往会与外边框重合,视觉上不雅观,往往要移除,很简单只需要对li的设置margin-bottom:-边框值,父元素再overflow: hidden即可.同理该方法稍微改动一下还可以处理border-right等

    1
    2
    3
    4
    5
    6
    7
    <ul class="list">
    <li>Test</li>
    <li>Test</li>
    <li>Test</li>
    <li>Test</li>
    <li>Test</li>
    </ul>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    .list{
    width:390px;
    margin:50px auto;
    background:#F4F8FC;
    border-radius:3px;
    border:2px solid #D7E2EC;
    overflow: hidden;
    }
    .list li{
    height:25px;
    line-height:25px;
    padding:5px;
    border-bottom:1px dotted red;
    margin-bottom: -1px;
    }


    demo演示

  • 图片与文字对齐问题
    图片与文字默认是居底对齐了。所以当图片与文字在一起的时候往往都是不对齐的。尤其图片较小时就更加明显了,我看到很多人使用vertical-align:middle;对齐。在火狐下效果是不错,但是IE下,虽然是效果好了些,但还是不够。
    如果,图片是个20像素*20像素左右的小图片,文字也差不多12px大,则使用vertical-align:text-bottom;是不错的个方法。还有个屡试不爽,兼容性不错的方法就是使用margin负值了。img标签是个很不错的标签,支持margin四个方向的正的和负的定位。一般img标签打头的小图标与文字对齐的话,img{margin:0 3px -3px 0;}可以说是公式版的东西,能实现效果和兼容性俱佳的对齐效果。
    demo演示

    借鉴文章:

    CSS布局奇淫巧计之-强大的负边距