布局和包含块

一个元素的尺寸和位置经常受其包含块(containing block)的影响。大多数情况下,包含块就是这个元素最近的祖先块元素内容区域,但也不是总是这样。在本文中,我们来过一遍确定包含块的所有因素。

当一个客户端代理(比如说浏览器)展示一个文档的时候,对于每一个元素,它都产生了一个盒子。每一个盒子都被划分为四个区域:

  1. 内容区
  2. 内边距区
  3. 边框区
  4. 外边距区

Diagram of the box model

许多开发者认为一个元素的包含块就是他的父元素的内容区,但这不一定正确。接下来让我们来看看,确定元素包含块的因素都有哪些。

包含块的影响

在学习如何确定元素包含块之前,先了解一下它的重要性。

元素的尺寸及位置,常常会受它的包含块所影响。对于一些属性,例如 width, height, padding, margin,绝对定位元素的偏移值(比如 position 被设置为 absolutefixed),当我们对其赋予百分比值时,这些值的计算值,就是通过元素的包含块计算得来。

确定包含块

确定一个元素的包含块的过程完全依赖于这个元素的 position 属性:

  1. 如果 position 属性为 staticrelative sticky,包含块可能由它的最近的祖先块元素(比如说 inline-block, block 或 list-item 元素)的内容区的边缘组成,也可能会建立格式化上下文 (比如说 a table container, flex container, grid container, 或者是 the block container 自身)。
  2. 如果 position 属性为 absolute ,包含块就是由它的最近的 position 的值不是 static (也就是值为fixed, absolute, relativesticky)的祖先元素的内边距区的边缘组成。
  3. 如果 position 属性是 fixed,在连续媒体的情况下 (continuous media) 包含块是 viewport ,在分页媒体 (paged media) 下的情况下包含块是分页区域 (page area)。
  4. 如果 position 属性是 absolutefixed,包含块也可能是由满足以下条件的最近父级元素的内边距区的边缘组成的:
    1. transformperspective 的值不是 none
    2. will-change 的值是 transformperspective
    3. filter 的值不是 nonewill-change 的值是 filter(只在 Firefox 下生效)。
    4. contain 的值是 layoutpaintstrictcontent(例如:contain: paint;
    5. backdrop-filter 的值不是 none(例如:backdrop-filter: blur(10px);

备注: 根元素(<html>)所在的包含块是一个被称为初始包含块的矩形。它具有视口(对于连续媒体)或页面区域(对于分页媒体)的尺寸。

备注: perspectivefilter 属性对形成包含块的作用存在浏览器之间的不一致性。

根据包含块计算百分值

如上所述,当某些属性被赋予一个百分比值时,它的计算值取决于这个元素的包含块。以这种方式工作的属性包括盒模型属性偏移属性

  1. heighttopbottom 属性根据包含块的 height 计算百分比值。
  2. widthleftrightpaddingmargin 属性根据包含块的 width 计算百分比值。

备注: 一个块容器(比如 inline-block、block 或 list-item 元素)要么只包含参与行级格式化上下文的行级盒子,要么只包含参与块级格式化上下文的块级盒子。只有包含块级或行级盒子的元素才是块容器。

示例

接下来的示例,都使用如下 HTML 代码:

html
<body>
  <section>
    <p>This is a paragraph!</p>
  </section>
</body>

下面的示例,只有 CSS 不同。

Example 1

这个示例中,P 标签设置为静态定位,所以它的包含块为 <section> ,因为距离最近的父节点即是她的包含块。

css
body {
  background: beige;
}

section {
  display: block;
  width: 400px;
  height: 160px;
  background: lightgray;
}

p {
  width: 50%; /* == 400px * .5 = 200px */
  height: 25%; /* == 160px * .25 = 40px */
  margin: 5%; /* == 400px * .05 = 20px */
  padding: 5%; /* == 400px * .05 = 20px */
  background: cyan;
}

Example 2

在这个示例中,P 标签的包含块为 <body> 元素,因为 <section> 不再是一个块容器,所以并没有形成一个格式上下文。

css
body {
  background: beige;
}

section {
  display: inline;
  background: lightgray;
}

p {
  width: 50%; /* == half the body's width */
  height: 200px; /* Note: a percentage would be 0 */
  background: cyan;
}

Example 3

这个示例中,P 元素的包含块是 <section>,因为 <section>positionabsolute 。P 元素的百分值会受其包含块的 padding 所影响。不过,如果包含块的 box-sizing 值设置为 border-box ,就没有这个问题。

css
body {
  background: beige;
}

section {
  position: absolute;
  left: 30px;
  top: 30px;
  width: 400px;
  height: 160px;
  padding: 30px 20px;
  background: lightgray;
}

p {
  position: absolute;
  width: 50%; /* == (400px + 20px + 20px) * .5 = 220px */
  height: 25%; /* == (160px + 30px + 30px) * .25 = 55px */
  margin: 5%; /* == (400px + 20px + 20px) * .05 = 22px */
  padding: 5%; /* == (400px + 20px + 20px) * .05 = 22px */
  background: cyan;
}

Example 4

这个示例中,P 元素的 positionfixed,所以它的包含块就是初始包含块(在屏幕上,也就是 viewport)。这样的话,P 元素的尺寸大小,将会随着浏览器窗框大小的变化,而变化。

css
body {
  background: beige;
}

section {
  width: 400px;
  height: 480px;
  margin: 30px;
  padding: 15px;
  background: lightgray;
}

p {
  position: fixed;
  width: 50%; /* == (50vw - (width of vertical scrollbar)) */
  height: 50%; /* == (50vh - (height of horizontal scrollbar)) */
  margin: 5%; /* == (5vw - (width of vertical scrollbar)) */
  padding: 5%; /* == (5vw - (width of vertical scrollbar)) */
  background: cyan;
}

Example 5

这个示例中,P 元素的 positionabsolute,所以它的包含块是 <section>,也就是距离它最近的一个 transform 值不为 none 的父元素。

css
body {
  background: beige;
}

section {
  transform: rotate(0deg);
  width: 400px;
  height: 160px;
  background: lightgray;
}

p {
  position: absolute;
  left: 80px;
  top: 30px;
  width: 50%; /* == 200px */
  height: 25%; /* == 40px */
  margin: 5%; /* == 20px */
  padding: 5%; /* == 20px */
  background: cyan;
}

参见