为什么明明只有一个 12px 的小元素,父容器却有 63px 高?

问题引入

html 复制代码
<style>
  .parent {
    font-size: 42px;
    line-height: 1.5;
    background: lightgray;
  }
  .child {
    display: inline-block;
    font-size: 12px;
    line-height: 1.5em;
    background: coral;
  }
</style>
<div class="parent">
  <div class="child">123456789</div>
</div>

看上述代码的页面渲染,是否会跟你想象的不太一样?为什么我的 .child 明明没有 padding 和 margin,它的周围却有莫名其妙的空白?

如果初看代码,你也觉得整个 .parent 类的高度应该跟 .child 一样,.child 上下的空间是不应该存在的话,那就推荐你认真了解一下 CSS 的 IFC(Inline Formatting Context,行内格式化上下文)和 strut 了。

空白的来源

我们先来看灰色的那一块高度到底怎么来的:

  • 父元素 .parent
    • font-size: 42px
    • line-height: 1.5
    • 计算得出父元素的行高为:63px。
  • 子元素 .child
    • font-size: 12px
    • line-height: 1.5em
    • 计算得出子元素自身内容的行高为:18px。

所以差距就在于 63 和 18 的差值,但问题是,.parent 里根本没有直接文字节点,里面只有 .child 的文字,为什么还是会按照 .parent 的高度撑起来呢?

Strut 的支撑

因为 .parent 是一个块级容器(<div>),并且它内部包含行内级元素(.child 设置了 display: inline-block),所以 .parent 建立了一个 IFC。

此时如果 .parent 有文字内容,浏览器会在 .parent 的内部,.child 的前面,悄悄塞入一个 strut.parentfont-size 是 42px,line-height 是 1.5,所以它的高度是 63px

现在 .parent 里面有两个元素参与排版:

  1. 高度为 63px 的隐形 strut。
  2. 高度为 18px 的 .child 盒子。

因为两者默认的 vertical-align 都是 baseline(基线对齐),所以浏览器会进行如下操作:

  1. 找到 strut 的基线。
  2. 找到 .child 的基线(对于 inline-block,基线是其内部最后一行文本的基线,这里就是 "123456789" 的基线)。
  3. 把这两条基线对齐

然后就会出现你看到的样子,明明 .child 那么小,却会被撑开。这在整个 .parent 里面只有单个元素的场景下看起来有点反直觉,但是试想一下,如果整句话存在多种行内元素的混排,有一个统一的行高是一个再正常不过的设计。

虽然,如果里面存在行内元素高于父元素的行高,还是会被撑起来......

html 复制代码
<style>
  .parent {
    font-size: 30px;
    line-height: 1.5;
    background: lightgray;
  }
  .child {
    display: inline-block;
    font-size: 12px;
    line-height: 1.5em;
    background: coral;
  }
  .child2 {
    display: inline-block;
    font-size: 50px;
    line-height: 1.5em;
    background: coral;
  }
</style>
<div class="parent">
  这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。
  <div class="child">123456789</div>
  这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。
  <div class="child2">123456789</div>
  这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。这是一段很长的内容。
</div>

知道 strut 会造成意外的支撑,那么怎么解决呢?一个比较 hack 的方法是把 .parent 的行高直接压制掉:

html 复制代码
<style>
  .parent {
    font-size: 0;
    line-height: 0;
    background: lightgray;
  }
  .child {
    display: inline-block;
    font-size: 12px;
    line-height: 1.5em;
    background: coral;
  }
</style>
<div class="parent">
  <div class="child">123456789</div>
</div>

但要说更靠谱的方法,我觉得还是得从消除 IFC 入手。

IFC

因为 IFC 的缘故,Div 里的行高会依照当前 Div 自己的 font-size 和 line-height 产生 strut。那么 IFC 本身是怎么产生的呢?

一句话概括就是:当一个块级容器内部直接包含行内级元素时,这个块级容器内部就会产生 IFC。

如果同时存在块级和行内容器呢?浏览器为了不让文字和块级元素打架,会偷偷创建一个 "匿名块盒" 把文字包裹起来。此时,包含文字的那个"匿名块盒"内部会产生一个 IFC,但最外层的 <div> 整体并不是一个纯粹的 IFC 环境。

我们明白了有 IFC 才会有 strut,那么想要消除这个 strut 只要把格式化上下文改掉就好了。

处理 IFC,CSS 还有 BFC、FFC 和 GFC。

在 CSS 的视觉格式化模型中,除了我们最常听到的 BFC (Block Formatting Context) 和 IFC (Inline Formatting Context) 之外,还有 FFCGFC

总共加起来,CSS 中主要有以下四种格式化上下文:

BFC (Block Formatting Context,块级格式化上下文)这个大家应该最熟。只要 float 不为 none、position 是 absolute 或 fixed、display 用了 inline-block / table-cell / flex / grid / flow-root(现代最常用),或者 overflow 不为 visible,就能触发。它的特点是内部元素垂直排列、外边距不会和外部重叠、能包住浮动元素,也不会被浮动元素盖住。清除浮动、防 margin 塌陷、自适应两栏布局,都是它的活。

html 复制代码
<style>
  .parent {
    font-size: 42px;
    line-height: 1.5;
    background: lightgray;
  }
  .child {
    display: block;
    font-size: 12px;
    line-height: 1.5em;
    background: coral;
  }
</style>
<div class="parent">
  <div class="child">123456789</div>
</div>

.child 改成 block,里面的文字就根据它自己产生 IFC,问题解决。

FFC (Flex Formatting Context,弹性格式化上下文),display 设成 flexinline-flex 就有。容器里的子元素会变成 flex item,不再按块级、内联那套规则排版,主轴和交叉轴上的空间分配、对齐都比较自由。适合导航栏、卡片居中、均分剩余空间这类一维布局。

GFC (Grid Formatting Context,网格格式化上下文),display 设成 gridinline-grid 就有。容器会被切成行和列,形成网格单元,子元素想放哪就放哪,跨行跨列也随意。整个页面的主体结构、仪表盘这类二维布局,基本就得靠它。

html 复制代码
<style>
  .parent {
    display: grid;
    font-size: 42px;
    line-height: 1.5;
    background: lightgray;
  }
  .child {
    display: inline-block;
    font-size: 12px;
    line-height: 1.5em;
    background: coral;
  }
</style>
<div class="parent">
  <div class="child">123456789</div>
</div>

.parent 设置成 flex 或者 grid 之后,子元素也不成 IFC 了,问题解决。

太长不读

strut 因为父元素开了 IFC 才存在 ,它本质上是一个看不见的"文本行",高度完全由父元素自己的 font-sizeline-height 决定。只要父元素里有行内级内容,把那一行支撑起来,哪怕你里面只放了一个小小的文字,行高也会比你预期高出一截。

所以再遇到这种"莫名的留白"问题,可以首先怀疑是 IFC 的问题,对应修复方法是:

  • font-size: 0; line-height: 0 压制 strut
  • 让子元素变成 display: block,它会自己内部重新建 IFC,不再使用父元素的 IFC
  • 或者干脆把父元素改成 flex / grid,子元素变成 flex item / grid item,绕开 IFC
相关推荐
IT_陈寒3 小时前
Python的GIL让我深夜加班,这破锁到底怎么折腾的
前端·人工智能·后端
乘风gg4 小时前
9 张 AI 生成的图,吃透任何一个前端项目
前端·ai编程·claude
moonsims4 小时前
低空量子无人机
前端·数据结构
breeze jiang4 小时前
React + WebGPU 在浏览器运行 DeepSeek:从 Worker 通信到流式生成
前端·javascript·react.js
.柒宇.4 小时前
运维常见面试题_02_数据库
运维·数据库·sql·面试
a1117764 小时前
家居生成器 Cartoon 开源项目 3D web
前端·开源
小林ixn4 小时前
前端卡顿终结者:用 useRef 把 Web Worker 请进 React 项目
前端·react.js·前端框架
sunly_4 小时前
React 三个重要概念:Ref、Props、State 详解
前端·javascript·react.js
weixin_431600444 小时前
前端对接 SSE 的两种常见方式
前端·后端·学习·ai·sse·nest.js