深入理解margin重叠以及负margin对元素大小的影响

CSS代码:

body
{
background-color:yellow;
 }
    .par {
	margin-top:10px;
	background-color:red;
        width:100px;
	 height:100px;

    }

HTML部分:

<body>
   <div class="par" id="par">
    </div>
 </body>
javascript部分:

window.οnlοad=function()
{
/*记住,通过window.getComputedStyle要通过window.onload以后才能完成,或DOMContentLoaded时候触发也行*/
//  var top=window.getComputedStyle(document.getElementById("par"),null)["width"];
//console.log(top);
var result=document.getElementById("par").getBoundingClientRect();
console.log(result);
}
note: getComputedStyle必须在onload或者DOMContentLoaded时候才能调用。这时候body的margin-top为8px,而par的margin-top是10px,因此会发生重叠,最后top属性为10px!left是8px,因为左右不会发生margin重叠的现象!

第一步:我们把body的css改成如下:

body
{
  background-color:yellow;
  border:1px solid red;
 }
note:添加边框以后margin-top就不会发生重叠,因此调用getBoundingClientRect结果为top:19px,left:(8+1)px;
第二步:我们把body的css修改如下:

body
{
  background-color:yellow;
  padding:1px;
 }
note:这时候margin也不会发生重合,getBoundingClientRect结果为top=8+1+10=19px,left=8+1=9px;
第三步:我们把css和html修改如下:

CSS部分:

	 body
	 {
		background-color:yellow;
	 }
    .par {
	margin-top:10px;
	background-color:red;
     width:100px;
	 height:100px;
      }
    .child {
        background-color:green;
	 width:100px;
	 height:100px;
	margin-top:20px;
    }
 HTML部分:

 <div class="par" id="par">
       <div class="child" id="child">
	   </div>
    </div>
JS部分:

window.οnlοad=function()
{
var result1=document.getElementById("child").getBoundingClientRect();
console.log(result1);
}
note:这时候会连续发生margin重叠,body的margin-top为8px,par的margin-top是10px,child的margin-top为20px, 同时都没有设置margin/padding,最后通过getBoundingClientRect的调用得出top=20px,left=8px; 注意:这时候body元素,child元素,par元素的通过getBoundingClientRect得到的top值都是20px,也就是距离文档顶部为20像素!在页面如何排列就很容易理解了!
第四步:我们给body添加一个border,CSS修改如下:

	 body
	 {
		background-color:yellow;
		border:1px solid red;
	 }
note:这时候id="par"和id="child"的div仍然会发生重叠,导致par的margin-top变成20px,但是因为其父元素具有border,所以不会继续发生重叠。于是getBoundingClientRect的值为20+1+8=29px(par和child都是29px),但是对于body来说, 因为他的margin-top没有同后面子元素发生重合而改变,所以其值为8px!
第五步:我们给id=“par”的div添加一个border,CSS如下:

body
	 {
		background-color:yellow;
		border:1px solid red;
	 }
    .par {
	margin-top:10px;
	background-color:red;
        width:100px;
	 height:100px;
      border:1px solid gray;
    }
    .child {
            background-color:green;
		  width:100px;
	     height:100px;
		 margin-top:20px;
    }
note:这时候都不会发生margin-top重叠的现象,所以通过getBoundingClientRect得到的id为child的div的top=8+1+10+1+20=40px,同时id为par的top为=8+1+10=19px;同时两者的left都=8+1=9px。body的top仍然是8px!
上面是关于父子元素之间的margin-top发生重叠的现象,这个方式只要父元素有margin或者padding就不会发生重叠,但是万一发生重叠那么父元素就会继承子元素重叠后的margin值,从而整体发生移动。下面我们计算两个兄弟元素margin-bottom和margin-top重叠的情况:
第一步:

CSS部分:

	 body
	 {
		background-color:yellow;
	 }
    .par {
	background-color:red;
         width:100px;
	 height:100px;
       margin-bottom:10px;
    }
    .child {
        background-color:green;
         width:100px;
	height:100px;
	margin-top:20px;
    }
HTML部分:

  <div class="par" id="par">
       </div>
 <div class="child" id="child"> </div>
JS部分:

window.οnlοad=function()
{
var result1=document.getElementById("child").getBoundingClientRect();
console.log(result1);
}

note:这时候id为par的元素和id为child的元素会发生margin-bottom和margin-top重叠的情况,最后child的margin变成了20px,也就是距离par的距离变成了20px。然而,par和body是父子关系,同时par没有设置margin-top所以无法和body发生margin-top重合的情况,最后id为child的div通过getBoundingClientRect得到top=20+100+8=128px; left=8px;注意:这时候par的元素的getBoundingClientRect的值为8px

第二步:我们给par元素加上一个border,CSS如下:

     body
	 {
		background-color:yellow;
	 }
    .par {
	background-color:red;
       width:100px;
	 height:100px;
       margin-bottom:10px;
       border:1px solid gray;
    }
    .child {
            background-color:green;
		  width:100px;
	     height:100px;
		 margin-top:20px;
         }
note: 这时候我们发现两个div的margin-top和margin-bottom依然发生了重叠,通过设置border/padding的方法不能解决margin-bottom/margin-top存在重叠的问题。这时候child的top为20+1+1+100+8=130px。那么如果我就是不希望两个div发生重叠呢,于是这里引入 BFC的概念,因为BFC强调: Box 垂直方向的距离由margin决定。属于同一个 BFC 的两个相邻 Box 的margin会发生重叠。
通过上面的css分析,这两个div同时属于body构建的BFC中(跟元素产生了BFC),于是发生了垂直方向的重叠,为了摆脱这种情况,我们不让他们同属于一个BFC,修改CSS代码如下:

 body
	 {
	   background-color:yellow;
	 }
    .par {
	background-color:red;
       width:100px;
	 height:100px;
     margin-bottom:10px;
     border:1px solid gray;
    }
    .child {
        background-color:green;
	 width:100px;
	  height:100px;
	margin-top:20px;
    }
	#bfc
	{/*通过overflow来触发元素的BFC*/
	  overflow:hidden;
	}
HTML如下:

 <div class="par" id="par"></div>
  <div id="bfc">
    <div class="child" id="child"></div>
  </div>

note: 这时候par和child就不是同属于body构建的BFC中了,于是他们的margin就不会发生重叠!这时候getBoundingClientRect输出child的top属性为=20+1+1+10+100+8=140px

问题1:负margin对元素大小有没有影响?

情况一:margin-bottom为负值的情况(当父元素没有指定高度的时候,会修改父元素的高度!)

<div style="border:1px solid red;" id="margin">
	<div style="height:50px;border:1px solid blue;margin-bottom:-10px" id="child"></div>
</div>
如果子元素没有margin-bottom:-10px,那么这时候显然父元素的offsetHeight应该是54px。但是我们给子元素一个margin-bottom,这时候父元素的边界就会往里面收缩,因此父元素offsetHeight变成了50-10+2+2=44px!
  var dom1=document.querySelector("#margin");
  console.log(dom1.offsetHeight);//打印44px
也就是说margin-bottom为负数会影响父元素的高度,但是其是否会影响子元素自身的高度呢?

     var dom1=document.querySelector("#child");
	   console.log(dom1.offsetHeight);//打印52px
	   console.log(dom1.clientHeight);//打印50px
	   //负margin-bottom不会影响元素的offsetHeight,clientHeight等元素大小
	   //其作用只会影响文档流的边界,也就是父元素不再仅仅包裹着子元素,而是
	   //父元素的边界会往上面缩,也就是其父元素的边界在子元素边界上面
从上面的测试结果来看,margin-bottom为负值只会影响父元素的高度 (前提是父元素没有明确指定height),无法影响子元素自身的高度!
情况二:margin-right/margin-left为负值的情况(父元素指定了宽度的情况下子元素也会把 自身拉长)
<div style="border:1px solid red;width:800px;margin:0 auto;" id="margin">
	<div style="height:50px;border:1px solid blue;margin-right:-10px" id="child"></div>
</div>
通过下面的分析我们知道,margin-right为负数只会改变元素自身的宽度,不会修改父元素的宽度

        var dom1=document.querySelector("#child");
	   console.log(dom1.offsetWidth);
	   //打印810px,也就是说这时候子元素比父元素还要宽10px,也就是margin-right负值使得元素尺寸变化!
	   console.log(dom1.clientWidth);
	   //打印808px=(800+10-2)=808px,画一个图分析还是很容易的,本来占据了父元素的width为800px,然后有加长了10px
	   //但是要减去自己的border为2px!
	  var dom2=document.querySelector('#margin');
	  console.log(dom2.offsetWidth);//打印802px
          console.log(dom2.clientWidth);//打印800px
但是margin-right要能够拉长元素的尺寸也是有条件的: 父元素有宽度尺寸限制(div元素)。如果没有指定父元素,这时候是无法拉长元素的

	<div style="height:50px;border:1px solid blue;margin-right:-10px;display:inline-block;width:100px;" id="child"></div>
js部分如下

   var dom1=document.querySelector("#child");
	   console.log(dom1.offsetWidth);
	   //打印102px,margin-right无法拉长自身!
	   console.log(dom1.clientWidth);
	 //打印100px,也就是margin-right为负值无法改变元素自身的尺寸!

margin-left为负数在这种情况下也会修改元素自身的尺寸大小

 <div style="width:800px;margin:0 auto;border:1px solid red;">
	<div style="border:1px solid blue;height:100px;margin-right:-100px;margin-left:-100px;" id="child"></div>
</div>
这时候元素自身的宽度受到margin-left/margin-right负值的双重拉伸作用

 var dom1=document.querySelector("#child");
	   console.log(dom1.offsetWidth);//打印1000px
	   console.log(dom1.clientWidth);//打印998px

margin-bottom通过收缩文档流改变了父元素的尺寸大小,margin-right/margin-left在父元素尺寸固定的情况下可以改变子元素自身的尺寸!其它情况下都无法通过margin负值修改元素本身的大小或者父元素的大小
除了上面这种特殊情况,对于margin-left/margin-top为负数的情况:
	<div style="height:50px;border:1px solid blue;width:800px;margin:0 auto;margin-left:-700px;margin-top:-40px;" id="margin"></div>
margin-left为负数,元素往左边移动,margin-top为负数元素往上面移动,但是移动的时候元素的尺寸是没有变化的

         var dom1=document.querySelector("#margin");
	   console.log(dom1.offsetWidth);//打印802px
	   console.log(dom1.clientWidth);//打印800px
	   console.log(dom1.offsetHeight);//打印52
	   console.log(dom1.clientHeight);//打印50
margin-right为负数边界往里面收缩,为正数表示边界往外扩张,同理margin-bottom为负数表示边界往内部收缩,为正数表示往外部扩张:

<div style="height:50px;border:1px solid blue;width:800px;margin-right:-100px;float:left;" id="margin">	
</div>
<div style="height:50px;border:1px solid blue;width:100px;float:left;" id="margin">	
</div>

这时候因为第一个div的margin-right为负数,表示元素右边边界往左边收缩了,所以后面的div元素会直接和第一个div重叠了!由浅入深漫谈margin一文中指出了:border+可视区域构成了我们的物理大小,而物理大小不随着margin负值的改变而改变(一般情况下是这样,因为clientWidth+border是不包含margin部分的,但是除了上面这种情况二);同时该文也提出了逻辑大小的概念,元素的逻辑大小会影响后续元素的显示,负数的margin-bottom会导致元素的边界收缩,从而后面的元素会占据收缩产生的那一部分文档流,从而形成后面元素覆盖前面元素的效果(从后面可以看出,后面元素的客户区无法覆盖前面元素的border);负数的margin-right也是一样的道理!但是,对于margin-left除了上面这种特殊情况可以用于改变元素的大小以外,其它情况都是导致元素向左移动;负数margin-top只会导致元素向上面移动;负数margin-right除了上面这种特殊情况可以修改元素的宽度以外都是导致元素边界收缩,使得后面的元素可以覆盖元素本身!该文给出了结论:

box 最后的显示大小等于 box 的 border 及 border 内的大小加上正的 margin 值。而负的 margin 值不会影响 box 的实际大小,如果是负的 top 或 left 值会引起 box 的向上或向左位置移动,如果是 bottom 或 right 只会影响下面 box 的显示的参考线。其中除了“而负的 margin 值不会影响 box 的实际大小”以外,我都是认同的,因为从上面的情况2来说,当父元素指定了宽度,但是子元素通过margin-left/margin-right负值却改变了其自身的元素大小!

问题2:负margin有那些用法,原理是什么?

解答:参见这篇文章可以知道margin-right为负值可以使得元素的子元素的宽度增加(和同时设置一个比父元素更宽的宽度是一样的效果);父元素设置了overflow:hidden有两个作用,作用一在于内部元素是浮动,overflow可以触发BFC,BFC计算高度的时候浮动元素也包含在里面,所以不会高度塌陷,作用二在于子元素宽度增加使得子元素可以存放所有的li元素,但是会有一部分超出了父元素的边界,所以这一部分内容要超出隐藏掉!这也是多列等高布局的原理所在!

  <div id="test">
        <ul>
            <li>子元素1</li>
            <li>子元素2</li>
            <li>子元素3</li>
            <li>子元素4</li>
            <li>子元素5</li>
            <li>子元素6</li>
        </ul>
    </div>
CSS布局如下:

  *{
     margin:0;
	 padding:0;
   }
   #test
   {
   /*一排存放3个li元素所需要的宽度是330px,但是这里只有320px*/
         width:320px;
	 border:1px solid red;
	 margin:0 auto;
	 height:210px;
  }
  ul
  {
      overflow:hidden;
	/*触发父元素的BFC,因为在计算BFC高度的时候父元素也包含在里面,
	同时overflow:hidden使得超过test的宽度可以隐藏!*/
      margin-right:-10px;/*和直接设置为width:330px效果一样*/
  }
    /*每一个li元素的宽度是110px*/
   li
   {
         width:100px;
	 margin-right:10px;
	 list-style:none;
	 background-color:#ccc;
	 height:100px;
        float:left;
	 margin-bottom:10px;
   }
其实这里的负数margin的效果和设置width是完全一样的!
用法2:去除最后一个li元素的border-bottom值
我们来看一个例子,如果上面元素有border-bottom同时又有margin-bottom:-1px那么后面的元素会覆盖前面元素的border,导致元素之间出现了覆盖,文档流也就相应的减少了

<div style="border-bottom:1px solid red;width:800px;margin:0 auto;height:100px;background-color:#ccc;margin-bottom:-1px;"></div>
<div style="border-bottom:1px solid red;width:800px;margin:0 auto;height:100px;background-color:green;"></div>
该例子会导致后面的div元素的绿色覆盖上一个元素的红色的底边框,同时文档流也减少了一个像素(注意:这里有背景色,如果没有背景色无法产生覆盖的效果)

同理,我们看看如何去除最后一个li的border-bottom,导致最后一个li元素的border-bottom不和父元素的border重合:

<ul id="test">
        <li>Test</li>
        <li>Test</li>
        <li>Test</li>
        <li>Test</li>
        <li>Test</li>
    </ul>
CSS部分:

 body,ul,li{margin:0;padding:0;}
    ul,li{list-style:none;}
    #test{
        margin:20px;
        width:390px;
        background:#F4F8FC;
        border-radius:3px;
        border:2px solid #D7E2EC;
    }
	/*每一个li的高度是35px*/
    #test li{
        height:25px;
        line-height:25px;
        padding:5px;
        border-bottom:1px dotted #D5D5D5;
	margin-bottom:-1px;
    }
JS部分

  var dom=document.querySelector("li");
	console.log(dom.offsetHeight);
	//每一个li的高度是36px=(25+10+1)=36px
	var ul=document.querySelector("ul");
	//如果没有margin-bottom:-1px那么该值为36*5+2*2=184px!
	//但是如果有了margin-bottom:-1px那么后面的元素会覆盖上面元素的border-bottom
	//最后得到184-5=179px!
	console.log(ul.offsetHeight);

那么我们有一个问题,后面的li元素虽然没有border-top,但是当上一个元素设置了margin-bottom:-1px时候,该元素的padding部分是否会覆盖掉上一个li元素的border-bottom?

解答:肯定会,但是如果后面的元素没有指定background-color那么是无法产生后面元素覆盖前面元素的效果的,除非有不同的背景色,而ul在这里的背景色是固定的,所以在视觉上就相当于没有重合一样的效果!

<div style="border:10px solid green;background-color:#ccc;">
	 <div style="border-bottom:1px solid red;;height:100px;margin-bottom:-1px;;">Test1</div>
	 <div style="border:1px solid red;;height:100px;">Test2</div>
</div>
因为第一个div子元素设置了margin-bottom:-1px,所以后面的元素的div的border会和上面的border完全重合,但是如果margin-bottom:-10px时候你会发现这样一个情况,那就是后面的元素和上面的元素的border都存在!见 该图
通过该例子应该学会margin-bottom为负值会导致 文档流往上收缩从而后面的元素会覆盖上面的元素的一部分,但是如果整个包裹元素的颜色是相同的,那么就不会产生后面的元素客户区覆盖上一个元素的border-bottom的效果,这就是上面的去除最后一个li元素的核心,因为#test元素是春色的,所以后面的li不会产生覆盖上一个li元素border-bottom的特效!

用法3:margin对那些元素有效果?

Block水平:不要告诉我你懂margin一文中明确指定了,对于block水平的元素四个方向都是可以设定的,但是对于标准浏览器中,如果父元素的第一个子元素采用了margin来和父元素撑开距离这时候就会出现上面所说的垂直方向的margin重叠现象,除非父元素明确指定了padding-top/border-top,这时候我们强烈建议使用父元素的padding-top来撑开距离,保证没有margin重叠的情况下还有利于维护(相对于为第一个子元素设定paddingTop来说)!

inline水平:对于内联元素中(非置换元素)来说margin-top/margin-bottom失效,但是margin-left/margin-right还有效果

内联元素中还有一类特殊的元素-置换元素,这些个元素img|input|select|textarea|button|label|object虽然是内联元素,但margin依旧可以影响到他的上下左右!他们区别一般inline元素(相对而言,称non-replaced element)是:这些元素拥有内在尺寸(intrinsic dimensions),他们可以设置width/height属性。他们的性质同设置了display:inline-block的元素一致。

浮动元素:CSS布局奇淫巧计之-强大的负边距一文指出了负margin对浮动元素的影响:

<div>
  <div class="float" style="background-color:#ccc;"></div>
  <div class="float" style="background-color:red;"></div>
  <!--我们让最后一个元素往左边移动100px-->
  <div class="float" style="background-color:blue;margin-left:-100px;"></div>
</div>
通过给浮动元素设定margin负值也能导致元素之间存在覆盖

.float
 {
   float:left;
   width:100px;
   height:100px;
   margin-right:-50px;
   /*所有的浮动元素右边界收缩了50px,后续的元素会在收缩的距离上面显示*/
 }
从这个例子我们知道,浮动元素也是受到负margin的影响的(浮动元素虽然脱离了文档流,但是对于inline水平的元素其仍然能够发现浮动元素的存在,所以说浮动不是完全意义上的脱离文档流)。有一点要注意: inline/inline-block水平的元素浮动后都会变成block水平的元素
    var dom=document.querySelector("#block");
   console.log(document.defaultView.getComputedStyle(dom,null)['display']);//inline-block变成了block水平的元素
绝对定位元素:

负数margin对于绝对定位元素的影响主要有:利用绝对定位元素来实现元素垂直水平居中,不过这种居中的实现依然要知道元素本身的尺寸!同时,张鑫旭在“absolute绝对定位的非绝对定位用法”一文中用到了margin对绝对定位元素的影响得出:absolute定位与margin定位其实是没有什么冲突的,无论absolute元素时候设置了left/top值,其margin属性值都是可以起作用的!
很多经典的例子可以参考:负值之美:负margin在页面布局中的应用我知道你不知道的负Margin等系列博客

总结:

(1)通过getBoundingClientRect可以获取到元素距离可视区域顶端的距离,因为body的margin默认是8像素,这是为什么要css reset!

(2)父子元素的margin-top的合并的问题的解决可以通过为元素设置border-top或者padding-top(但是建议父元素下第一个子元素用padding代替margin,该思想可以参考文章用padding还是用margin),然而兄弟元素的margin-top/margin-bottom的重叠就需要构建BFC了,因为默认如果两个元素属于同一个BFC就会发生margin-top/margin-bottom重叠现象!

(3)除了上面提到的"情况二"(父元素指定了宽度,而子元素通过margin-right/margin-lef负值t改变了元素自身的大小),margin-left/margin-right/margin-bottom/margin-top并不会改变元素本身的大小。margin-left为负数只会导致元素往左边移动,margin-top为负数是往上移动,而margin-bottom会改变元素的逻辑大小,使得后面的元素可能会覆盖元素自身(但是不会覆盖元素的border);margin-right也仅仅是修改元素的右边界,使得后续的元素可能覆盖元素自身!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值