margin 为负值产生的影响和常见布局应用

前言

前几天去了一家公司面试前端,问了我双飞翼的布局,说实话,之前真没好好研究过实现原理。
面试回来,查了下,主要都是用到了 margin-left 负数产生的效果。
所以今天整理些 margin:负数会对哪些元素或者定义产生影响、margin 为负值在 web 布局中的应用做下总结。(不能说最全,我已经尽力收集整理)

一、margin 为负值产生的影响

对于自身的影响

当元素不存在 width 属性或者(width: auto) 的时候,负 margin 会增加元素的宽度,看下下面的例子

<div class="contaienr1">
  <div class="box1">
         I dont have the width
    </div>
</div>
.contaienr1 {
  width: 400px;
  height: 400px;
  border: 1px solid red;
  margin: 0 auto;
}

.box1 {
  border: 1px solid green;
  margin-left: -20px;
  margin-right: -20px;
}

android margin 负数无效 margin是负数_css


在 jsrun 中打开示例

可以看到 box1 增加了 20px 宽度,margin-left 和 margin-right 都是可以增加宽度

注意:
margin-top 为负值不会增加高度,相应的会减少自身的高度,进而产生向上位移
margin-bottom 为负值不会产生位移,会减少自身的供 css 读取的高度。

<div class="wrapper">
  <div class="box">
    box
  </div>  
</div>

<div class="wrapper">
  <div class="box2">
    box
  </div>  
</div>
.wrapper {
  margin-top: 100px;
  border: 1px solid red;
}
.box {
  width: 50%;
  margin-bottom: -25px;
  background-color: rgba(90, 243, 155, 0.8);
  height: 50px;
}

.box2 {
  width: 50%;
  margin-top: -25px;
  background-color: rgba(90, 243, 155, 0.8);
  height: 50px;
}
var x = $('.box').height()
console.log(x);

在 jsrun 中打开示例

android margin 负数无效 margin是负数_绝对定位_02


下文的应用段落就会利用这个特点做一个多列等高布局。

对文档流的影响

元素如果用了 margin-left: -20px;毋庸置疑的自身会左偏移 20px 和定位(position: relative) 有点不一样的是,在其后面的元素会补位,也就是后面的行内元素会紧贴在此元素的之后。总结,不脱离文档流不使用 float 的话,负 margin 元素是不会破坏页面的文档流。

<div>
  <h2>文档流-行内元素</h2>
  <div>
    我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章<span class="inline-margin">我是一大堆文章</span>我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章
  </div>
</div>
<div>
  <h2>文档流-块元素</h2>
  <div>
    我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章我是一大堆文章
    <div class="box">
      我是个盒子
    </div>
     我是后面的元素我是后面的元素我是后面的元素我是后面的元素我是后面的元素我是后面的元素我是后面的元素我是后面的元素我是后面的元素我是后面的元素
  </div>
</div>
.inline-margin {
  color: #eee;
  margin-left: -50px;
  background: rgba(0, 0, 0, 0.1)
}


.box {
  height: 50px;
  width: 50px;
  background: blue;
  margin-top: -25px;
  margin-left: -25px;
}

在 jsrun 中打开示例

android margin 负数无效 margin是负数_绝对定位_03

所以如果你使用负 margin 上移一个元素,所有跟随的元素都会被上移。

对浮动元素的影响

先定义 3 个 box

<div class="container1">
  <div class="flb flbox1">box1</div>
  <div class="flb flbox2">box2</div>
  <div class="flb flbox3">box3</div>
</div>
.flb {
  float: left;
  width: 100px;
  height: 100px;
}
.flbox1{background-color: rgba(33, 114, 214, 0.8);}
.flbox2{background-color: rgba(255, 82, 0, 0.8);}
.flbox3{background-color: rgba(90, 243, 151, 0.8);}

android margin 负数无效 margin是负数_css_04


demo1, 给他们都加上 margin-left: -25px

.flb{
    float: left;
    width: 100px;
    height: 100px;
    margin-left: -25px;
}
.flbox1{background-color: rgba(33, 114, 214, 0.8);}
.flbox2{background-color: rgba(255, 82, 0, 0.8);}
.flbox3{background-color: rgba(90, 243, 151, 0.8);}

android margin 负数无效 margin是负数_margin_05


可以看出 3 个盒子都向左移动 25px;

box1自身向左移动了25px,box2又覆盖了其25px,所以我们就看到了“宽度”为50px的box1

box2,box3以此类推!

让我再看看 margin-left: -50px;的情况

android margin 负数无效 margin是负数_绝对定位_06


如果只给 box3 设置 margin-left: -200px

.flb{
    float: left;
    width: 100px;
    height: 100px;
    
}
.flbox1{background-color: rgba(33, 114, 214, 0.8);}
.flbox2{background-color: rgba(255, 82, 0, 0.8);}
.flbox3{background-color: rgba(90, 243, 151, 0.8);
    margin-left: -200px;}

android margin 负数无效 margin是负数_margin_07


看一看到 box3,向左偏移了 200px,完全将 box1 覆盖了,box3 下面还能隐约的看到了 box1.

在 jsrun 中打开上述示例

总结
负 margin 会改变浮动元素的显示位置,即使我的元素写在 DOM 的后面,我也能让它显示在最前面。圣杯布局、双飞翼布局啊什么的,都是利用这个原理实现的。

对绝对定位影响

<div class="absolute"></div>
.absolute{
    position: absolute;
    top:50%;
    left:50%;
    height: 200px;
    width: 200px;
    background-color: #ccc;
    margin-top: -100px;
    margin-left: -100px;
}

android margin 负数无效 margin是负数_文档流_08


在 jsrun 中打开示例

对于绝对定位元素,负 margin 会基于其绝对定位坐标再偏移,
唯一的缺点就是你必须知道这个绝对定位元素宽度和高度才能设置负 margin 值使其居中浏览器窗口,
若对于不确定宽度和高度可以用

transform: translate3d(-50%,-50%,0);

使用translate3d可以开启GPU加速,就不用cpu去从新计算所有点素位置,开启GPU加速后,GPU自动将这个元素放在一个新的“层”,直接偏移这个“层”来提高渲染速度(个人理解,若有错误欢迎指正)。

二、margin 为负值的常见布局应用

左右固定,中间自适应(双飞翼)

双飞翼的好处:

  1. 可以让主要内容出现在 dom 结构的前面,先将主要内容渲染
  2. 中间自适应,两边固定宽度的效果
<div class="main">
  <div class="main-content">
    main content
  </div>
</div>
<div class="left">left</div>
<div class="right">right</div>
* {
  margin: 0;
  padding: 0
}

.main {
  float: left;
  width: 100%;
}
.main .main-content {
  margin: 0 210px;
  background-color: rgba(33, 114, 214, 0.8);
  height: 500px;

}

.left {
  width: 200px;
  float: left;
  background-color: rgba(255, 82, 0, 0.8);
  margin-left: -100%;
  height: 200px;
}

.right {
  width: 200px;
  height: 200px;
  background-color: rgba(90, 243, 151, 0.8);
  float: left;
  margin-left: -200px;
}

在 jsrun 中打开示例

去除列表右边框

利用负 margin 增加宽度的特点,举个在实例中应用例子

对于图片列表,我会常常设计成两边对齐,中间元素平均分布,类似下面的布局。

android margin 负数无效 margin是负数_margin_09


想让每一排最后一个对齐父元素的右边框,一般都两种处理,第一种会在给个循环体内的最后一个添加一个float:right属性或者做特殊处理,但这样还需要后端配合,我们自己能解决的就尽量自己解决。还有就是用css3的flexbox布局能解决这个两边对齐,但是这种布局兼容性不好,你的用户用IE的话,不推荐!

so,负margin就能发挥其增加元素宽度的特点,完美的解决这个问题!

<div class="container">
    <ul class="list">
        <li>我是一个列表</li>
        <li>我是一个列表</li>
        <li>我是一个列表</li>
        <li>我是一个列表</li>
        <li>我是一个列表</li>
        <li>我是一个列表</li>
        <li>我是一个列表</li>
        <li>我是一个列表</li>
        <li>我是一个列表</li>
        <li>我是一个列表</li>
    </ul>
</div>
* {
  padding: 0;
  margin: 0;
}
ul {
  list-style: none;
}
.container{
        margin:0 auto;
        width: 500px;
        border: 1px #ccc solid;
        margin-bottom: 20px;
        overflow: hidden;
}
.list{
    overflow: hidden;
    margin-right: -10px;

}

.list li{
    width:92px;
    height: 92px;
    background-color: #ccc;
    margin-bottom: 20px;
    float: left;
    margin-right: 10px;
}

android margin 负数无效 margin是负数_margin_10


在 jsrun 中打开示例

计算公式:
假设一横列展示的数量为x,元素见的间距为y,父距宽度z
则元素的宽度=(z-y(x-1))/x
本例中li的宽度为(500-10(5-1))/5=92

负边距 + 定位 : 水平垂直居中

上面已经举例,用了负margin会向对应方向偏移的特点!

去除列表最后一个 li 元素的 border-bottom

很多情况下,我们会用 li 标签来模拟表格,再给 li 标签添加下边距的时候,最后一个 li 标签表格会和父元素的边框靠在一起,会显得整个 table 的底部边框会比其他的边粗一些!

<ul class="table">
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
 </ul>


 <ul class="table t2">
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
   <li>I am li</li>
 </ul>
* {
  margin: 0;
  padding: 0;
}

ul {
  list-style: none;
  margin-top: 20px;
  width: 200px;
  margin-left: auto;
  margin-right: auto;
}

ul.table {
  border: 1px #ccc solid;
}

ul.table li {
  border-bottom: 1px #ccc solid;

}

.t2 {
  margin-top: 20px;
  margin-bottom: 20px;
}

ul.table.t2 li:last-child {
  margin-bottom: -1px;
}

android margin 负数无效 margin是负数_android margin 负数无效_11


在 jsrun 中打开示例

多列等高

利用 margin-bottom 为负值会减少 css 读取元素高度的特性,加上 padding-bottom 和 overflow: hidden,就能做一个未知高度的多列等高布局(当然也可以用 table)

<div class="container">
   <div class="left">
     height: 50px
   </div>
   <div class="main">
     height: 100px;
   </div>
   <div class="right">
     height: 30px;
   </div>
 </div>
.container {
  margin: 0 auto;
  width: 100%;
  overflow: hidden;
}

.left {
  height: 50px;
  width: 33.33%;
  margin-bottom: -5000px;
  padding-bottom: 5000px;
  float: left;
  background-color: rgba(33, 114, 214, 0.8);
}

.main {
  height: 100px;
  margin-bottom: -5000px;
  width: 33.33%;
  float: left;
  padding-bottom: 5000px;
  background-color: rgba(255, 82, 0, 0.8);
}
.right {
  width: 33.33%;
  float: left;
  margin-bottom: -5000px;
  padding-bottom: 5000px;
  background-color: rgba(90, 243, 151, 0.8);
}

android margin 负数无效 margin是负数_margin_12


虽然设置了 5000 的内边距,但是我用-5000的外边距去抵消掉,所以对于父元素来说(上文所说的 css 能读取的高度),他还是原来的高度(但其自身实际高度为 5000px + 本来高度),然后父元素在加上 overflow: hidden;以最高的高度进行裁切,所以就有了看起来"等高"的 3个 div。