CSS

你不知道的 CSS 之包含块

百分比宽高与绝对定位偏移到底相对谁算?搞懂 containing block 与初始包含块,布局阶段的几何信息才说得清。

#layout #containing-block #interview

你不知道的 CSS 之包含块

盒模型、border-box 很多人熟,但 包含块(containing block) 一听就懵——它平时「看不见」,却决定 width: 50%left: 10px 相对谁算。

包含块决定元素的尺寸与百分比参照区域

简单说:元素的尺寸与位置(以及百分比值的计算)受包含块影响。 规范见 CSS2 containing block

不同定位方式下的包含块规则

浏览器布局阶段,几何信息就是结合 DOM、Computed Style 与包含块算出来的;样式计算 里算不定的百分比宽高,到这一步才定稿。

最简例子:50% 相对谁?

<body>
  <div class="container">
    <div class="item"></div>
  </div>
</body>
.container {
  width: 500px;
  height: 300px;
  background-color: skyblue;
}

.item {
  width: 50%;
  height: 50%;
  background-color: red;
}

item 是 250×150?对。但更准确的说法是:百分比相对包含块。这里包含块是最近祖先块容器的 内容区(content box)——即 container 的内容区 500×300,50% 才是 250×150。

50% 相对 container 内容区,结果为 250×150

平时说「相对父元素宽高」多半没错,但严谨表述是 相对包含块

初始包含块

根元素 html 的包含块叫 初始包含块(initial containing block)

  • 大小通常等于 视口(viewport)
  • 原点在视口左上角
  • 绝对定位、固定定位 的重要参照

非根元素:怎么找包含块?

position包含块由谁建立
static / relative最近 块容器内容区 边缘
fixed视口(初始包含块)
absolute最近 position 不为 static 的祖先的 padding 区 边缘

absolute 不看「父元素」,看「定位祖先」

<body>
  <div class="container">
    <div class="item">
      <div class="item2"></div>
    </div>
  </div>
</body>
.container {
  width: 500px;
  height: 300px;
  background-color: skyblue;
  position: relative;
}

.item {
  width: 300px;
  height: 150px;
  border: 5px solid;
  margin-left: 100px;
}

.item2 {
  width: 100px;
  height: 100px;
  background-color: red;
  position: absolute;
  left: 10px;
  top: 10px;
}

item2 的包含块是 container(有 position: relative),不是中间的 item

item2 的包含块是 container,而非中间的 item

transform 也会建立包含块

absolute / fixed 的包含块还可能是满足下列条件的 最近祖先的 padding 区

  • transform / perspective 不是 none
  • will-changetransformperspective
  • filter 不是 none(或 Firefox 下 will-change: filter
  • contain: paint

item 加上 transform: rotate(0deg)

.item {
  transform: rotate(0deg);
}

包含块变为 item 自身:

transform 在 item 上建立新的包含块

这也是 transform 常和层叠、合成一起讨论的原因——不仅影响包含块,还影响 分层与绘制

包含块知识要点一览

规范例题

<html>
  <head>
    <title>Illustration of containing blocks</title>
  </head>
  <body id="body">
    <div id="div1">
      <p id="p1">This is text in the first paragraph...</p>
      <p id="p2">
        This is text
        <em id="em1">
          in the
          <strong id="strong1">second</strong>
          paragraph.
        </em>
      </p>
    </div>
  </body>
</html>

无额外 CSS 时:

元素包含块
html初始包含块
bodyhtml
div1body
p1、p2div1
em1p2
strong1p2

strong1 的包含块是 p2 而非 em1,因为 em1 不是 块容器;规则要求 最近的块容器 的内容区。

加上:

#div1 {
  position: absolute;
  left: 50px;
  top: 50px;
}

div1 的包含块变为 初始包含块body 仍是 static,向上找不到定位祖先):

元素包含块
div1初始包含块
p1、p2div1

再给 em1 定位:

#em1 {
  position: absolute;
  left: 100px;
  top: 100px;
}
元素包含块
em1div1
strong1em1

更多示例见 MDN:Containing block

规范例题中的 DOM 树与包含块关系

小结

  • 包含块 决定百分比与定位偏移的参照区域,不是笼统的「父元素」。
  • static/relative 看最近块容器的 内容区absolute 看定位祖先的 padding 区fixed 看视口。
  • transform 等属性会 新建包含块,布局结果可能「突然变了」。
  • 布局阶段算几何信息时离不开包含块;改样式触发的 reflow 见 reflow 与 repaint