木子手记

CSS属性探秘之margin

金金 ( 更新) CSSCSS属性探秘

1. margin概述

1.1 定义

Margins in CSS serve to add both horizontal and vertical space between boxes. –W3C

翻译:CSS中的margin用来添加盒子之间的水平和垂直间隙。

margin 同时也被成为 外边距。按照它的W3C定义,很明了的知道,它主要是用来限定盒子边框与其他相邻盒子边框之间的距离。如同马路上汽车与汽车之间的安全距离一样,但是CSS中的外边距可比汽车之间的安全距离自由多了,可以零距离和负距离,你汽车来个负距离试试。

margin是个复合属性,属性简写设置所有四个方向的外边距,而其他外边距属性只设置其各自方向的宽度。这些属性应用于所有元素,但垂直外边距在非替代行内元素上不起作用

1.2 margin与盒模型的关系

如下图: Box Modle

无论是哪种盒模型,bordermargin 的值,都不参与盒模型尺寸的计算。如其定义所说,它只是用来添加盒子之间的水平和垂直间隙

1.3 margin与排版页面

margin 的设置,会影响页面排版。

把参与页面布局的盒子想象成一个个国家,一旦一个盒子声明了外边距,就是意味着,从外边距的边界开始,里面的领土都是其私有领地,任何不经过通过同意都不能进入它的领地与领空。如果它设置负边距,向上或者向左移动,旁边的盒子里面会占领它下边或者右边的空间(文档流的特性,尽量填充满整个页面)。

这个特性注意需要和相对定位区别开来。 margin layout 因为正常文档元素通过设置margin属性,既有文档流的流动性,又没有脱离正常文档流,还可以用于定位,参与页面的正常排版,所有经常会用来实现一些页面布局。后面会有介绍。

2. 属性值的设置

由于 margin 是一个复合属性,与 borderpadding 一样,由四个值组成,方向是:上→右→下→左。分别是:

margin: margin-top margin-right margin-bottom margin-left;

其它略写方式,如设置为三个值,两个值,一个值的情况,这里就不做赘述。

每个 margin 方向上可设置值如下表:

描述
auto 浏览器计算外边距。
length 规定以具体单位计的外边距值,比如像素、厘米等。默认值是 0px。
% 规定基于父元素的宽度的百分比的外边距。
inherit 规定应该从父元素继承外边距。

2.1 值为auto时的表现

常用的就是让一个块级元素水平居中:margin:0 auto;,这句代码确实有效,还十分实用,但是有人就是爱较真,会忍不住k可能会问出下面两个问题:

  1. 为什么 auto 能让块级元素水平居中?
  2. 为什么通过margin不能实现块级元素垂直居中?

在W3C的文档关于块级非置换元素的宽度计算有如下规定:

此时,包含块宽度=margin-left + border-left-width + padding-left + width + padding-right + border-right-width + margin-right。(关于包含块的概念,这里暂时不做解释,以后会有文章专门介绍)。盒子的宽度计算时,如果有一个值设置为 auto,则该使用值由等式求出。

  1. 若 width 设置 auto, 则其他 auto 值变成 0 且 width值由等式求出。
  2. 若 margin-left 与 margin-right 都是 auto, 则他们的使用值相等。这会将元素水中居中在包含块中。

由于块级元素(基本上所有的块级元素都是块级非置换元素),所有它的包含块会占据整行。在通过上面第2条,就很容易得出结论。

另外,如果只是设置 margin-left: auto; 会怎样呢? 这个疑问大家可以自己试试。

从上面得出的结论是:正常文档流中的块级元素,一旦width或者水平margin值设置为auto,那意味着尽可能的占据。

关于第二个问题,想通过margin的auto来实现垂直居中,这个就不好意思了,肯定是行不通的。原因也很简单:标准规定:垂直方向上的外边距计算,都是采取保守策略,尽量采取最小值,如果是auto,则当作0

2.2 值为百分比时的表现

先看一个例子:

<style type="text/css">
    .demo-container {
        width:1000px;
        height: 600px;
        background: #f2f3f5;
    }
    .demo {
        margin: 10% 5%;
        background-color: pink;
    }
</style>
<body>
    <div class="demo-container">
        <div class="demo"></div>
    </div>
</body>

大伙猜猜,div.demo的margin计算值为多少?

A: 计算值为 margin: 100px 30px;

Q: 确定么?

A: 确定!

Q: 恭喜你答错了!正确答案是: margin: 100px 50px;,没想到吧

A: 不可能,你丫忽悠我吧,给我解释解释那个垂直方向外边距的50px从哪来的

Q: 简单呀, 1000*5%

A: …

我猜很多人都猜不到这个结果,怎么可以高度和宽度的百分比都参照包含块的宽度进行计算呢?

关于值设置为%时的表现,W3C上只有一句:

百分比按照生成框的包含块的宽度计算。

个人理解,在一个复合属性中,如果可以几个值都可以设置百分比,其百分比的参照肯定一致。这是算是方便计算吧。

如果想参照包含块的高度行不行呢? 答案是肯定的,行!只要CSS中设置如下:

.demo{
    -webkit-writing-mode: vertical-rl;
    writing-mode: tb-rl; 
}

没想到吧,原来都是书写模式搞的鬼!

3. 折叠外边距

3.1 什么是折叠外边距?

代码:

.demo1 { margin: 10px 0;}
.demo2 { margin: 20px 0;}
.demo3 { margin: 30px 0;}

margin折叠

看到上面结果,是不是有点奇怪,为啥第一个Div和第二个Div之间的外边距只有20px,而不是30px。原因是,这里垂直方向上的外边距发生了折叠。

在CSS中,两个以上的框(可能是兄弟,也可能不是)之间的相邻外边距可以被合并成一个单独的外边距。通过此方式合并的外边距被称为折叠,且产生的已合并的外边距被称为折叠外边距

相邻垂直外边距合并,除了:

  • 根元素的框的外边距不合并。
  • 如果一个有间隙的元素的上、下外边距相邻,则其外边距将与下面的兄弟的外边距合并,但产生的边距不会与父亲区块的下外边距合并。

*水平外边距不会合并。(其实和书写模式有相关!)*。

3.2 什么情况下会产生折叠外边距

产生规则:处于同一个块级上下文(BFC)中的块元素,没有行框、没有间隙、没有内边距边框隔开它们,这样的元素垂直边缘毗邻,则称之为相邻

通过上面规则介绍可知,产生折叠外边距必要有个三个条件:都是同一个BFC中的块元素没被隔开垂直边缘毗邻

那什么是垂直边缘毗邻?

  • 元素的上外边距和其属于常规流中的第一个孩子的上外边距。
  • 元素的下外边距和其属于常规流中的下一个兄弟的上外边距。
  • 属于常规流中的最后一个孩子的下外边距和其父亲的下外边距,如果其父亲的高度计算值为 auto。
  • 元素的上、下外边距,如果该元素没有建立新的块级格式上下文,且 min-height 的计算值为零、height 的计算值为零或 auto、且没有属于常规流中的孩子。

3.3 如何避免折叠外边距

因为产生折叠外边距的初衷是用来文字排版,但是目前却经常发生在页面布局中,这反而有点出人意料,折叠外边距在此时就这样被嫌弃了,需要被避免产生。

避免的方法也很简单:

  1. 让两个块元素处于不同BFC中
  2. 用间距、内边距或者边框将之隔开

4. margin在布局中妙用

4.1 经典的圣杯布局

圣杯布局是指左右两栏固定宽度,中间部分自适应的一种常见布局,如下图:

圣杯布局

主要代码

  • css部分:
.demo-container {
    overflow: hidden; /* 清除浮动 */
    padding: 0 200px;
}
.left-nav, .right-nav, .main-wrap {
    float: left;
    height: 300px;
}
.main-wrap {
    width: 100%;
}
.left-nav, .right-nav {
    position: relative;
    background-color: pink;
    width: 200px;
}
.left-nav {
    margin-left: -100%;
    left: -200px;
}
.right-nav {
    margin-left: -200px;
    right: -200px;
}
  • html部分:
<div class="demo-container">
    <div class="main-wrap">main</div>
    <div class="left-nav">left</div>
    <div class="right-nav">right</div>
</div>

核心思想:利用负外边距和相对定位相互抵消来实现。

4.2 单列变三列(负外边距的妙用)

把单个列表变成三列: 单列变三列

实现超简单:

  • html部分
<ul> 
    <li class="col1">Eggs</li>
    <li class="col1">Ham<li> 
    <li class="col2 top">Bread<li> 
    <li class="col2">Butter<li> 
    <li class="col3 top">Flour<li> 
    <li class="col3">Cream</li> 
</ul>
  • css部分
ul {list-style:none;}
li {line-height:1.3em;}
.col2 {margin-left:100px;}
.col3 {margin-left:200px;}
.top {margin-top:-2.6em;} /* the clincher */

通过对.top的添加margin-top:-2.6em。所有的元素会完美的对齐好。使用负边距会比使用相对定位好很多,因为你只需要给新的一列的第一个元素添加负边距即可。

4.3 其他应用

margin的其他使用,主要还是利用负外边距与float、内联框等,实现水平上的布局。

5.总结

之前从未想过,简单如margin这样的属性,仔细研究起来,居然也有这么多知识点。 这个周末过的很颓废,窝在家看看电视剧,就过了…

参考:

W3C margin

margin系列之keyword auto

[负边距详解][9]

[9]: http://segmentfault.com/a/1190000003942591

金金
一枚奔跑在前端路上的男子