第 22 章 盒模型

在理解 CSS 是如何控制页面显示效果的时候,我们必须要掌握盒模型(Box Model)和定位(position)机制。CSS 借助于盒模型和定位机制,结合文档树,能够精确、高效地控制内容在页面中的位置,从而实现页面的布局。

22.1 盒模型的概念

所有 HTML 元素,在页面的呈现过程中,都遵循 CSS 制定的盒模型(Box Model),盒模型是一个包含外边距、边框线、内边距以及内容的矩形容器。具体如下图所示:

CSS 盒模型

图 22.1: CSS 盒模型

从图中我们可以看到,元素的盒模型由外边距 (margin)、边框线 (border)、内边距(padding)以及元素的内容(content)构成,CSS 对外边距、边框线、内边距的控制可以分方向进行,也可以整体控制,如上图中的 TM 就表示上侧外边距、LM 表示左侧外边距、RM 表示右侧外边距、BM 表示底侧外边距。

在 CSS 中,直接用来描述盒模型的属性有 margin、border、padding、width、height。需要注意的是,CSS 提供的宽度属性(width)和高度属性(height)指的是内容区域(content)的宽度和高度,而不是整个盒模型的宽度和高度。整个盒模型的大小应该包括内外边距及边框的宽度。

盒模型的宽度 = “margin-left” + “border-left” + “padding-left” + “width” + “padding-right” + “border-right” + “margin-right

盒模型的高度 = “margin-top” + “border-top” + “padding-top” + “height” + “padding-bottem” + “border-bottom” + “margin-right

22.2 设置盒子大小

在 CSS 中,可以使用盒模型的 widthheight 属性为除行内元素之外的大多数元素设置高度和宽度。行内元素的宽度和高度取决于自身内容。

宽度和高度的值可以为百分比、带单位的长度或者是 auto。如:

#content {
    width: 90%;
    height: 300px;
}

百分比的计算是按照父元素的大小来计算,而不是按照本身的大小。如果没有指定宽度,盒模型就是用默认值 100%,也就是说和所在容器的宽度一样,如果没有指定高度,浏览器则会根据内容自动计算出高度大小。

除了 widthheight 之外,CSS 还提供了 max-heightmax-widthmin-heightmin-width 属性。

22.3 设置外边距

外边距用来控制元素之间的距离,在 CSS 中,使用 margin 属性来控制外边距,外边距是透明的空间量。内容之间适当的空间能够增加内容的可读性。除行内元素不接受上下外边距的设定外,其他元素都可以设定外边距。

margin 属性的值可以是带单位的长度、百分比和 auto。设置外边距的方式如下:

div {
    margin: 10px;
}

上述规则表示 div 元素四侧的外边距为 10 个像素。

div {
    margin: 10px 20px;
}

上述规则表示 div 元素上下侧的外边距为 10 个像素,左右侧的外边距为 20 像素。

div {
    margin: 10px 20px 5px;
}

上述规则表示 div 元素上侧的外边距为 10 像素,左右两侧的外边距为 20 像素,下侧的外边距为 5 像素。

div {
    margin: 10px 20px 0 5px;
}

上述规则表示 div 元素上侧的外边距为 10 像素,右侧的外边距为 20 像素,下侧外边距为 0,左侧外边距为 5 像素。

除了使用 margin 统一设定元素外边距外,CSS 还提供了 margin-topmargin-rightmargin-bottom 以及 margin-left 四个属性分别设定各侧的外边距。

需要特别说明的是,当元素左右两侧的外边距取值 auto 时,这个元素就会在所在容器中居中。如:

div {
    margin: 0 auto;
}

22.4 设定内边距

CSS 中使用 padding 表示内边距,内边距和外边距在很多方面是相似的。padding 的值可以是带单位的长度或者是百分比。padding 属性值中没有 autopadding 属性值可以是 1-4 个值,其意义与 margin 相同。padding 也可以分侧指定,如 padding-toppadding-rightpadding-bottompadding-left

22.5 设置边框

边框是进行信息组织是的一种有效手段,通过边框的使用,能够区分不同类型的信息,而且边框还是一种装饰手段,能在组织信息的同时美化页面。CSS 提供了 border-styleborder-widthborder-color 以及 border 元素来控制边框的样式、宽度以及颜色。如:

#footer {
    border-style: dashed;
    border-width: 1px;
    border-color: #ccc;
}

上述规则将使得 idfooter 的元素四周拥有灰色、1 个像素宽度的虚线边框。

其中 border-style 的属性值用来指定边框线的样式,默认值为 none,也就是没有边框,因此,在定义边框属性时,border-style 实际上是必须要指定的,常用的值有:solid, double, dashed, dotted, and none

border-width 的属性用来指定边框先的宽度,宽度值为带单位的长度(如 1px)或关键字(thinmediumthick),宽度默认值为 medium

border-color 属性用来指定边框颜色,其值可以为文本、16 进制颜色值和 rgb 函数值,颜色的默认继承元素内容的颜色。

CSS 还提供了快速设定边框样式的属性——border,如上述的样式可以这样简写:

#footer {
    border: dashed 1px #ccc;
}

border 的值中必须要指定的是 border-style,其他两个可以任意组合,并且对出现的先后顺序也无要求。和 marginpadding 属性类似,border-styleborder-width 以及 border-color 可以接受 1-4 个值,用以分别指定不同侧面的边框样式,如下面的样式规则使得 idfooter 的元素上下两侧没有边框,左右两侧的边框样式为虚线、宽度为 medium、颜色和该元素的内容颜色一致:

#footer {
    border-style: none dashed;
}

最后,CSS 也提供了分别指定不同方向上边框的机制:border-topborder-rightborder-bottom 以及 border-left。如下面的样式规则将使得 idfooter 的元素拥有 1 个像素宽的红色虚线上边框:

#footer {
    border-top: dashed 1px red;
}

22.6 圆角

border-radius 属性,允许我们为元素设置圆角效果。

border-radius 属性接受长度单位,包括百分比和像素。border-radius 如果只有一个值,则设定四个角,俩值、三个值和四个值的情况与 marginpadding 类似,都按顺时针方向设定。例如:

.border-football {
  border-radius: 15px 75px;
}

上述代码设定左上、右下角为半径 15 像素的圆角,而右上和左下的圆角半径为 75 像素。

22.7 外边距叠加

先来看看 W3C 对于外边距叠加的定义:

In CSS, the adjoining margins of two or more boxes (which might or might not be siblings) can combine to form a single margin. Margins that combine this way are said to collapse, and the resulting combined margin is called a collapsed margin.

在CSS中,两个或多个毗邻的普通流中的盒子(可能是父子元素,也可能是兄弟元素)在垂直方向上的外边距会发生叠加,这种形成的外边距称之为外边距叠加。

我们可以注意定义中的几个关键字:毗邻两个或多个垂直方向普通流

  • 毗邻说明了他们的位置关系,没有被padding、border、clear和line box分隔开。
  • 两个或多个两个或多个盒子是指元素之间的相互影响,单个元素不会存在外边距叠加的情况。
  • 只有垂直方向的外边距会发生外边距叠加。水平方向的外边距不存在叠加的情况。
  • 普通流

W3C只对out of flow作了定义:

An element is called out of flow if it is floated, absolutely positioned, or is the root element.An element is called in-flow if it is not out-of-flow.

从定义中我们可以知道只要不是floatabsolutely positionedroot element时就是普通流。

22.7.1 什么时候会发生外边距叠加

外边距叠加存在两种情况:一是父子外边距叠加;二是兄弟外边距叠加。

W3C对于什么是毗邻的外边距也有定义:

Two margins are adjoining if and only if: - both belong to in-flow block-level boxes that participate in the same block formatting context - no line boxes, no clearance, no padding and no border separate them - both belong to vertically-adjacent box edges, i.e. form one of the following pairs:

  • top margin of a box and top margin of its first in-flow child
  • bottom margin of box and top margin of its next in-flow following sibling
  • bottom margin of a last in-flow child and bottom margin of its parent if the parent has “auto” computed height
  • top and bottom margins of a box that does not establish a new block formatting context and that has zero computed “min-height”, zero or “auto” computed “height”, and no in-flow children

从定义中我们可以很清楚的知道要符合哪些情况才会发生外边距折叠:

都属于普通流的块级盒子且参与到相同的块级格式上下文中 没有被padding、border、clear和line box分隔开 都属于垂直毗邻盒子边缘:

  • 盒子的top margin和它第一个普通流子元素的top margin
  • 盒子的bottom margin和它下一个普通流兄弟的top margin
  • 盒子的bottom margin和它父元素的bottom margin
  • 盒子的top margin和bottom margin,且没有创建一个新的块级格式上下文,且有被计算为0的min-height,被计算为0或auto的height,且没有普通流子元素

demo1:

.parent1 {
    height: 20px;
    background: yellow;
    margin-bottom: 20px;
}
.parent2 {
    margin: 20px 0 30px;
}
.parent3 {
    height: 20px;
    background: green;
    margin-top: 20px;
}
.child {
    background: red;
    height: 20px;
    margin: 40px 0 30px;
}

<div class="parent1"></div>  
<div class="parent2">  
    <div class="child"></div>
    <div class="child"></div>
    <div class="child"></div>
</div>  
<div class="parent3"></div>  

这个demo里的.parent2和第一个.child的top margin叠加,导致.parent1和.parent2之间的边距为40px。

demo2:

还是用上面的代码,.parent2中的.child中的top margin和bottom margin发生外边距叠加,它们之间的外边距为40px。

demo3:

还是上面的代码,.parent2中的最后一个.child发生bottom margin叠加,.parent2和.parent3之间的边距为30px。

demo4:

.demo {
    height: 30px;
    background: red;
}
.margin-test {
    margin: 20px 0 30px;
}

<div class="container">  
    <div class="demo"></div>
    <div class="margin-test"></div>
    <div class="demo"></div>
</div>

这个demo是上面的第四种情况,元素自身的外边距top和bottom发生折叠,我们可以看出.container的高度为90px,这里可以看到margin-test的top和bottom外边距发生了折叠。

22.7.2 如何避免外边距叠加

上面讲了外边距的叠加,那如何避免呢,其实只要破坏上面讲到的四个条件中的任何一个即可:毗邻、两个或多个、普通流和垂直方向。

W3C也对此做了总结:

  • Margins between a floated box and any other box do not collapse (not even between a float and its in-flow children).
  • Margins of elements that establish new block formatting contexts (such as floats and elements with “overflow” other than “visible”) do not collapse with their in-flow children.
  • Margins of absolutely positioned boxes do not collapse (not even with their in-flow children).
  • Margins of inline-block boxes do not collapse (not even with their in-flow children).
  • The bottom margin of an in-flow block-level element always collapses with the top margin of its next in-flow block-level sibling, unless that sibling has clearance.
  • The top margin of an in-flow block element collapses with its first in-flow block-level child"s top margin if the element has no top border, no top padding, and the child has no clearance.
  • The bottom margin of an in-flow block box with a “height” of “auto” and a “min-height” of zero collapses with its last in-flow block-level child“s bottom margin if the box has no bottom padding and no bottom border and the child”s bottom margin does not collapse with a top margin that has clearance.
  • A box“s own margins collapse if the”min-height" property is zero, and it has neither top or bottom borders nor top or bottom padding, and it has a “height” of either 0 or “auto”, and it does not contain a line box, and all of its in-flow children"s margins (if any) collapse.
  • 浮动元素不会与任何元素发生叠加,也包括它的子元素。
  • 创建了BFC的元素不会和它的子元素发生外边距叠加。
  • 绝对定位元素和其他任何元素之间不发生外边距叠加,也包括它的子元素。
  • inline-block元素和其他任何元素之间不发生外边距叠加,也包括它的子元素。
  • 普通流中的块级元素的margin-bottom永远和它相邻的下一个块级元素的margin-top叠加,除非相邻的兄弟元素clear。
  • 普通流中的块级元素(没有border-top、没有padding-top)的margin-top和它的第一个普通流中的子元素(没有clear)发生margin-top叠加。
  • 普通流中的块级元素(height为auto、min-height为0、没有border-bottom、没有padding-bottom)和它的最后一个普通流中的子元素(没有自身发生margin叠加或clear)发生margin-bottom叠加。
  • 如果一个元素的min-height为0、没有border、没有padding、高度为0或者auto、不包含子元素,那么它自身的外边距会发生叠加。