Skip to content

盒模型

1. 元素宽度问题

问题:当想要让两个元素并排显示,宽度占满一行,可以设置 float: left 均向左浮动,宽度设置 width: 50% ,理想情况下可以符合要求,但如果某一个元素设置了内边距,则不会并排,此时两个元素实际宽度加起来已经超过了 100% 宽度。

原因:当给一个元素设置宽高时,设置的是内容的宽高,所有内边距、边框、外边距都会追加到该宽度上。

1.1 避免魔术数值

解决上述问题最简单的方法就是减少一个元素的宽度,比如将第二个元素宽度设为 46%,但这种方式并不可靠,46% 是一个魔术数值,不是一个理想的值,而是通过样式试验出来的。

编程中不推荐魔术数值,因为往往难以解释一个魔术数值生效的原因。

一个替代魔术数值的方式是让浏览器帮忙计算,使用 calc() 函数,如 whidth: calc(30% - .5em)

1.2 调整盒模型

  • 在 CSS 中可以使用 box-sizing 属性调整盒模型行为。
  • box-sizing 默认值为 content-box,任何指定的宽高都只会设置内容盒子的大小。
  • 设置为 border-box 后,宽高会成为内容、内边距、边框的大小总和。

1.3 全局设置 border-box

  • 建议在项目之初全局设置 border-box,能省去很多不必要的麻烦,但为了不影响第三方的样式,必要时需要给第三方组件的顶级容器恢复成 content-box
  • 盒模型通常不会被继承,需要通过 inherit 强制继承。
css
::root {
  /* 根元素设置 */
  box-sizing: border-box; 
}

*
::before,
::after {
  /* 其它所有元素继承 */
  box-sizing: inherit; 
}

/* 将第三方恢复,避免影响样式 */
.third-party-component {
  box-sizing: content-box;
}

1.4 给列之间加上间隔

使用 border-box 之后,如果想给列之间加上间隔,可以给某一元素加上外边距,再调整元素的宽度:

css
.mian {
  float: left;
  width: 70%;
  background-color: #fff;
  border-radius: .5em;
}

.sidebar {
  box-sizing: border-box;
  float: left;
  /* 减去外部留白 */
  width: 29%;
  margin-left: 1%;
  padding: 1.5em;
  background-color: #fff;
  border-radius: .5em;
}

如果外边距与宽度单位不一致,可以使用 calc() 计算宽度。

2. 元素高度问题

  • 通常需要避免给元素指定明确的高度。普通文档流是为有限的宽度和无限的高度设计的。
  • 容器的高度最好由容器内容决定,而不是容器自己决定。

普通文档流是指网页元素的默认布局行为。行内元素跟随文字的方向从左到由排列,块元素独占一行,前后有换行。

2.1 控制溢出行为

当明确设置一个元素的高度,内容可能会溢出容器。

overflow 属性可以控制溢出内容的行为,可以设置下面 4 个值:

  • visible:默认值,所有内容可见,即使溢出容器边缘。
  • hidden:溢出容器内边距边缘的内容被裁剪,不可见。
  • scroll:容器出现滚动条,通过滚动查看剩余内容。
  • auto:只有内容溢出时才会出现滚动条。

建议使用 auto,大多数情况下,不希望滚动条一直出现。

2.2 百分比高度的备选方案

要让百分比高度生效,需要保证父元素有明确的高度。

2.2.1 等高列

为了让两个元素并列等高,可以设定确定的高度,但高度值太大了就会在容器底部留下大片空白,太小了就会溢出。最好是让元素自己决定高度,并让更矮的元素拓展高度与更高的元素相同。 通过 CSS 表格布局和 Flexbox 可以实现这种效果。

2.2.2 CSS 表格布局

给容器设置 display: table,给两列元素设置 display: table-cell 即可实现等高列。

css
.container {
  display: table;
  /* 明确指定宽度 */
  width: 100%;
  /* 设置单元格间距使等高列分隔 */
  border-spacing: 1.5em 0;
}

.main {
  display: table-cell;
  box-sizing: border-box;
  width: 70%;
  background-color: #fff;
  border-radius: .5em;
}

.sidebar {
  display: table-cell;
  box-sizing: border-box;
  width: 30%;
  /* 外边距不会生效 */
  margin-left: 1.5em;
  padding: 1.5em;
  background-color: #fff;
  border-radius: .5em;
}

注意

  • 默认情况下,显示为 table 的元素宽度不会拓展到 100%,因此需要明确指定宽度。
  • 外边距不会作用于 table-cell 元素。

为了让等高列有间距,可以使用表格元素的 border-spacing 属性设置单元格间距。该属性接受两个长度值:水平间距和垂直间距。但这样会产生一个副作用:该属性也会作用于表格的外边缘。

如果想消除对等高列左右外边距的影响,可以使用负外边距,这需要给整个表格包裹一层新容器:

css
.wrapper {
  margin: 0 -1.5em;
}

提示

  • 标准文档流中,未设置宽度时,块级元素的水平 margin 会通过盒模型间接改变内容区域的宽度。如果值为正会压缩宽度,负值会拉伸。

2.2.3 flexbox

给容器设置 display: flex 就会变成弹性容器,子元素默认等高,因此很容易实现等高列。

css
.container {
  display: flex;
  width: 100%;
}

.main {
  box-sizing: border-box;
  width: 70%;
  background-color: #fff;
  border-radius: .5em;
}

.sidebar {
  box-sizing: border-box;
  width: 30%;
  margin-left: 1.5em;
  padding: 1.5em;
  background-color: #fff;
  border-radius: .5em;
}

警告

除非别无选择,否则不要明确设置元素高度。

2.3 使用 min-height 和 max-height

可以使用 min-heightmax-height 指定最小高度或最大高度,而不是明确值,这样元素可以在范围内自动决定高度。

类似的属性还有 min-widthmax-width

2.4 垂直居中内容

为什么 vertical-align 声明不生效?

  • vertical-align 声明只会影响行内元素或者 table-cell 元素。
  • 对于行内元素来说,它控制该元素跟同一行内其它元素的对其关系。比如,可以用它控制一个行内的图片跟相邻文字对齐。
  • 对于显示为 table-cell 的元素,它控制了内容在单元格内的对齐。如果页面用了 CSS 表格布局,那可以用 vertical-align 实现垂直居中。
css
.container {
  height: 20vh;
  display: table-cell;
  vertical-align: middle;
}

CSS 中最简单实现垂直居中的方法就是给容器相等的上下内边距,让容器和内容自行决定高度。不管容器中内容是行内元素,块元素或是其它形式,这种方式都有效。

css
.container {
  padding: 2em 0;
}

垂直居中指南:

  1. 可以用一个自然高度的容器吗? 给容器加上相等的上下内边距,让内容居中。
  2. 容器需要指定高度或避免使用内边距吗? 对容器使用 display: table-cellvertical-align: middle
  3. 可以用 Flexbox 吗? 如果可以则用 flex 布局居中。
  4. 容器内的内容只有一行文字吗? 设置一个大的行高,让它等于理想的容器高度,容器如果自动高度则会拓展到能够容纳行高。
  5. 容器和内容高度都知道吗? 绝对定位,只有当前面方法都无效时才推荐这种方法。
  6. 不知道内部元素高度? 用绝对定位结合变形(transform)。

3. 负外边距

  • 不同于内边距和边框宽度,外边距可以设置负值。
  • 对于设置了宽度的块元素,如果左边或顶部设置负外边距,元素就会向左或向上移动,右边或底部设置,则自身不会移动,而会把后面的元素拉过来。
  • 对于没设置宽度的块元素,左右负外边距会把元素向左向右拉伸,顶部设置则元素上移动,底部设置则把下面的元素拉过来。

4. 外边距折叠

4.1 文字折叠

外边距折叠的主要原因与包含文字的块之间的间隔有关。

段落默认有 1em 的上外边距和下外边距,但前后叠放两个段落时,外边距不会相加,而会折叠只产生 1em 间距。

注意

折叠外边距的大小等于相邻外边距中的最大值。

4.2 多个外边距折叠

  • 即使两个元素不是相邻的兄弟节点也会产生外边距折叠,比如父子元素的顶部外边距就会折叠。
  • 在没有其它 CSS 的影响下,所有相邻的顶部和底部外边距都会折叠。
html
<main>
  <h2>Come join us!</h2>
  <div>
    <p>hello world!</p>
  </div>
</main>

上述代码中,<h2> 底部外边距,<div><p> 顶部的外边距会折叠,最终 <h2><p> 间隔是三者中最大值。

说明

只有上下外边距会产生折叠,左右外边距不会。

4.3 容器外部折叠

如果不想让外边距发生折叠,可以采取下面的方法:

  • 容器和内容之间:对容器使用 overflow: auto 或非 visible 的值,防止内部元素的外边距跟容器外部的外边距折叠。这种方式副作用最小。
  • 容器和内容之间:在两个外边距之间加上边框或者内边距,防止折叠。
  • 容器和内容之间:如果容器为浮动元素、内联块、绝对定位或固定定位时,外边距不会在它外面折叠。
  • 容器和内容或内部元素之间:当使用 Flexbox 布局时,弹性布局内的元素之间不会发生外边距折叠。网格布局同理。
  • 当元素显示为 table-cell 时不具备外边距属性,因此不会折叠。此外还有 table-row 和大部分其它表格显示类型,但不包括 tabletable-inlinetable-caption

5. 容器内的元素间距

容器内边距和内容的外边距相互作用:

html
<aside class="sidebar">
  <a href="/twitter" class="button-link">follow us on Twitter</a>
  <a href="/facebook" class="button-link">like us on Facebook</a>
</aside>
css
.sidebar {
  width: 30%;
  padding: 1.5em;
  box-sizing: border-box;
  background-color: #fff;
  border-radius: .5em;
}

.button-link {
  display: block;
  padding: 0.5em;
  color: #fff;
  background-color: #0090C9;
  text-align: center;
  text-decoration: none;
  text-transform: uppercase;
}

此时要给两个按钮增加间距,但不能给第一个加上边距,否则加上容器的内边距,会间隔很大不协调,可以选中第二个链接加上顶部边距:

css
/* 选中链接的兄弟元素 */
.button-link + .button-link {
  margin-top: 1.5em;
}

如果后面有其他不同元素,仍需单独加上间距,在某些情况下有更好的处理方式。

5.1 更通用的解决方案:猫头鹰选择器

猫头鹰选择器:* + *,可以选中所有元素后面跟着的兄弟元素,即有着相同父级的非第一个子元素。

css
/* 放在body后面,就只会选中body内的元素 */
body * + * {
  margin-top: 1.5em;
}

通过猫头鹰选择器,就不需要单独针对元素设置间距,但在某些不想加外边距的地方需要覆盖。

通常只在有并列元素或多列布局时这样使用。

6. 总结

  • 总是全局设置 border-box,以便得到预期的元素大小。
  • 避免明确设置元素高度,以免出现溢出问题。
  • 使用现代布局技术,如 Flexbox 或 display: table 实现等高列或垂直居中。
  • 如果外边距行为奇怪,就增加措施预防外边距折叠。
  • 某些情况使用猫头鹰选择器,能带来很大便捷。