CSS排版布局篇(2):文档流(Normal Flow)

现在进入 CSS 的核心底层------"文档流(Normal Flow)"。

如果我们把 CSS 比喻为"排版的语言",

那么"文档流"就是它的语法基石世界物理法则

理解它,就能明白 CSS 世界中一切"位置、层次、空间"的根源。


初步认识

为什么会有"文档流"?------从排版问题说起

问题的起点:如何"自动排好内容"?

HTML 早期的目标是"结构化信息",而非"绘图"。

网页内容是一种可变信息流

  • 文字会换行;
  • 图片大小不一;
  • 浏览器窗口宽度也不固定。

💡 所以浏览器必须自己"推算出"元素该放哪。

于是它采用了一套最简单、最符合阅读逻辑的规则:

从上到下、从左到右地排列元素。

这套默认规则,就被称为------

👉 文档流(Normal Flow)


文档流是什么?------网页的"空间分配算法"

我们可以把浏览器渲染过程想象成排版机器人:

1️⃣ 它读取 HTML,构建 DOM 树;

2️⃣ 它识别每个元素的类型(块、行内等),如果是块就垂直从上往下流动放置,如果是行内就水平从左往右流动放置;

3️⃣ 它按规则依次计算这些盒子的位置与大小。

于是,一个"流"被建立起来:

plain 复制代码
┌─────────────────────────┐
│                         │   Header(块)
├─────────────────────────┤
│ 文字(行内) 图片(行内) │   段落(块)
└─────────────────────────┘

就像水流一样,元素一个接一个地"流动"与"堆叠"

文档流并非属性,而是浏览器的默认排版机制。

所有布局系统(float、flex、grid、position)

都是对这条"流"的改造与重写。


文档流的两种流动方式(两大格式化上下文)

CSS 世界中,流并非单一,而是分为两种基本形态:

一种是块状排布,一种是行内排布。

格式化上下文 对应元素 排布方向 典型表现
BFC(Block Formatting Context) 块级元素(div、p、section) 垂直方向从上到下 每个元素独占一行
IFC(Inline Formatting Context) 行内元素(span、a、img) 水平方向从左到右 内容自动换行对齐

块级布局(BFC)------"垂直堆叠的世界"

块级元素的默认特性

  • 独占一行;
  • 宽度自动撑满父容器;
  • 高度由内容决定;
  • 外边距可折叠(margin collapse)。

例:

plain 复制代码
<div>第一个块</div>
<div>第二个块</div>

效果:

plain 复制代码
第一个块
第二个块

浏览器的计算过程:

plain 复制代码
1. 第一个div放到最上面;
2. 第二个div在第一个的下方;
3. 计算它们的margin、padding、border;
4. 排列出纵向序列。

🧠 这就是 Block Flow:从上到下的垂直流。


行内布局(IFC)------"文字流动的世界"

行内元素的默认特性

  • 不换行;
  • 只占内容宽度;
  • 高度由行高控制;
  • line-heightvertical-align 控制。

例:

plain 复制代码
<p>这是 <span>行内文字</span> 排版</p>

效果(横向流):

plain 复制代码
这是 行内文字 排版

浏览器的排版逻辑:

plain 复制代码
1. 创建"行框(line box)";
2. 将每个行内盒(inline box)放入;
3. 超出宽度时换行,形成下一行;
4. 根据行高(line-height)对齐基线。

🧠 这就是 Inline Flow:从左到右的水平流。


现在我们初步了解了文档流的基本概念,下面为了真正理解什么是文档流,以及文档流的复杂应用(层级嵌套),我们需要理解文档流的设计思路与物理模型


文档流的物理模型------纵横联合到层级嵌套

排版的出发点:空间的分配与内容的对齐

我们先回到历史(1996年 CSS1 时代)看设计动机:

当时的网页结构是这样的:

plain 复制代码
<body>
  <h1>标题</h1>
  <p>这是第一段文字。</p>
  <p>这是第二段文字。</p>
</body>

目标:让网页像书页那样,

  • 段落之间有上下间距(块级排列);
  • 段落内部的文字能自动换行(行内排列)。

于是浏览器的设计者思考:

  • 整个页面的**大框架(段落之间)**是"块与块之间"的垂直排列;
  • 每个段落内部是"行与行之间"的水平排列。

所以他们做了一个非常关键的层级分工设计

层级 控制对象 负责的任务 对应概念
外层(Block,块级上下文) 各个"块"之间 控制整体空间分区、垂直排布 "块流"
内层(Inline,行内上下文) 块内部的文字与小元素 控制行内内容、文字换行与对齐 "行内流"

于是,一个块级盒(如 <p>)内部可以继续存在"行内上下文",来处理文字排版。

这就好比一本书的排版:

  • 页与页之间的排布、边距控制 → 块级流
  • 每一页中段落内文字、图片的排列 → 行内流

什么是流?

我们把整个页面想象成一堆"水流系统":

名称 类比 流动方向 排布对象
块流(Block Flow) 大水渠 垂直方向流动 一块块盒子
行内流(Inline Flow) 小水流 水平方向流动 一行行文字或小图
  • 块流(Block Flow) :像一条从上到下流动的主河道,每个块级盒子(如 <div><p>)是这条河道里的一块块"浮板",依次堆叠;
  • 行内流(Inline Flow):是某个浮板上流动的小溪水,它在"块"里面横着流,装着文字、图片等小内容。

所以整个 CSS 排版世界是:

"河道(块流)里装浮板(块盒),浮板上流小溪(行内流)。"

plain 复制代码
Block Flow
 ├─ line box
 │   ├─ inline box(文字、图片、链接)
 │   └─ ...
 └─ 下一个块

即:

块级元素负责"垂直排版"

行内元素负责"行内排版"

它们形成了一个完整的"流体体系":

  • 外层控制方向与空间分配;
  • 内层控制内容对齐与换行。

纵横联合

块流(Block Flow)

当浏览器渲染文档时,首先会创建一个"块级上下文"(Block Formatting Context,简称 BFC)。

在这个上下文中:

  • 每个块级元素 (如 <div><p><section>)都像一个"盒子";
  • 它们从上到下依次排列;
  • 每个盒子之间有间距(margin),不会互相重叠;
  • 它们各自决定内部空间的"宽度"和"高度"范围。

简而言之,块流负责决定空间的外框结构。

例如:

plain 复制代码
<div>
  <p>第一段文字</p>
  <p>第二段文字</p>
</div>

这时:

  • <div> 创建了一个块级上下文;
  • <p> 元素作为两个块,从上到下排
  • 每个 <p> 的宽度默认占满父级的宽度(称为"块级伸展")。

行内流(Inline Flow)

当我们进入某个块的内部,比如一个 <p>,就进入了"行内格式化上下文"(Inline Formatting Context,简称 IFC)。

细心的同学读到这里一定会深感疑惑,为什么<p><div>同样作为块级元素,<div>的内部就是BFC,而这里<p>的内部就是IFC?这其实涉及到更深层次的一个设计,这里我们需要先了解行内流的含义,理解了基础,我们才可以更好地深入,下面我会带大家去理清缘由。

在这里:

  • 所有文字、行内标签(如 <span><a><img>)都在同一行内水平排列;
  • 如果一行放不下,就自动换行
  • 每一行被看作一个"行框"(line box);
  • 每个字符或行内盒都位于行框的"基线"上对齐。

例如:

plain 复制代码
<p>你好,<span>世界</span>!</p>

这里:

  • <p> 是块级盒;
  • 它的内部产生了若干行盒(line box)
  • 每个行盒内包含若干 inline box(行内盒)
  • 这些行内盒再由字符盒(glyph box)组成。

文档流树形结构
plain 复制代码
文档流(Document Flow)
│
├── 块级格式化上下文(BFC) → 决定外层布局(垂直流动、盒子分布)
│     ├── 块盒(block box)...
│     └── 每个块内生成行内格式化上下文
│
└── 行内格式化上下文(IFC) → 决定内层排版(水平排列、基线对齐)
      ├── 行盒(line box)
      │    ├── 行内盒(inline box)
      │    │    └── 字形盒(glyph box)
      │    └── 图片盒(img box)
      └── 下一个行盒

块流解决"空间怎么划分",行内流解决"内容怎么对齐"。

外层负责"方向与空间分配",内层负责"文字与细节对齐"。

上面的各个盒子是什么意思呢?让我们继续往下看:


CSS 的"盒族系统"------文档流的基本构件

要理解真正的排版机制,必须知道每个"盒"是干什么的。

文档流中的盒子是分层的,就像一个嵌套的套娃:

plain 复制代码
文档流(Document Flow)
│
├── 块级格式化上下文(BFC)
│     ├── 块盒(block box)
│     │     └── 行内格式化上下文(IFC)
│     │            ├── 行盒(line box)
│     │            │     ├── 行内盒(inline box)
│     │            │     │     ├── 字形盒(glyph box)
│     │            │     │     └── 图片盒(img box)
│     │            │     └── 下一个行内盒
│     │            └── ...
│     └── 下一个块
└── 下一个 BFC

我们依次拆开👇


块盒(Block Box)
  • 由块级元素(divpsection)生成;
  • 决定垂直方向的布局
  • 每个块盒形成一行(即"块流");
  • 控制 margin 合并、宽度自适应、清除浮动等行为。

你可以理解为:

块盒 = 页面结构的"主干骨架"。


行盒(Line Box)
  • 块盒中的文字或行内内容会被分割成"行";
  • 每一行文字对应一个 line box;
  • line box 的高度由该行中最高的元素决定。

举例:

plain 复制代码
<p>Hello <strong>World</strong>!</p>

这里 <p> 生成一个块盒,内部的"Hello World!" 形成一个 行盒(line box)

一行文字就是一个 line box。


行内盒(Inline Box)
  • 每一个行内元素(如 <span><a>em)都会生成一个 inline box;
  • inline box 在行盒内按顺序水平排列
  • 它们的高度由字体行高(line-height)和对齐方式(vertical-align)决定。

你可以理解为:

inline box 是"文字块"或"行中小容器"。


字形盒(Glyph Box)
  • 每一个字符(字形)对应一个 glyph box;
  • 它决定每个字的实际形状与占位;
  • 它是浏览器排版的最底层单位(真正被绘制到屏幕上的)。

CSS 属性如 letter-spacingfont-varianttext-transform

都在 glyph 层面起作用。


图片盒(Image Box)
  • 行内图片(<img>inline SVG)生成的盒;
  • 在 IFC(行内格式化上下文)中作为 inline-level box;
  • 它也参与基线对齐、line-height 计算。

为什么要分这些盒?

这是因为 CSS 的排版逻辑是分层进行的:

层级 作用 举例 对应上下文
Block Box 控制垂直排列 段落、div、section BFC
Line Box 控制每行内容 每一行文字 IFC
Inline Box 控制行中元素 span、a IFC
Glyph Box 控制字形形态 "A"、"你" 字体引擎层
Image Box 控制图片 <img> IFC

这就像打印机排字一样:

页面是由块组成的,

块中有行,

行中有行内内容,

行内内容由字与图组成。


关键认识:盒子的"包裹关系"
  • 块盒(block box) 包裹一组 行盒(line box)
  • 行盒(line box) 包裹若干 行内盒(inline box)
  • 行内盒(inline box) 进一步包裹 字形盒 / 图片盒

即:

块流包裹行流,行流包裹字形流。


层级嵌套

盒子的阵营------格式化上下文**(Formatting Context)**

CSS 的排版机制规定了:

每个盒子都有"自身的格式化上下文 "(注意这个上下文不一定是由自己创造的,就像一个人属于某一阵营,但这个阵营不见得是自己建立的,也可能是加入了别人的阵营),并遵循就近支配原则


关键分层:盒子 ≠ 格式化上下文
概念 本质 作用
盒子(Box) 元素生成的视觉容器(有宽高、边距等) 放内容、显示样式
格式化上下文(Formatting Context) 一种排版空间/规则系统 决定盒子如何排列

一句话理解:

盒子是"被排版的对象",

上下文是"排版的环境"。


谁创建谁?

不是每个盒子都会创建 一个新的上下文。

但每个盒子都属于某个上下文

这就是关键。

举个类比:

"每个学生都属于某个班级,但不是每个学生都有自己一个人的班级。"


默认状态下:格式化上下文延续共享------没事用不着自立门户

我们用最简单的例子来说明:

plain 复制代码
<div class="parent">
  <p>文字1</p>
  <p>文字2</p>
</div>

浏览器解析时:

1️⃣ <html> 创建根块级格式化上下文(root BFC)

2️⃣ .parent 是块盒(display: block),它被放进根 BFC 中。

3️⃣ .parent自己并不创建新 BFC (除非触发条件,如 overflow:hidden / float 等)。

4️⃣ .parent 的两个 <p> 也被放进同一个(根)BFC。

📊 结构图:

plain 复制代码
BFC(root)
 ├── parent
 │    ├── p
 │    └── p

所以虽然"每个盒子都存在于某个格式化上下文中",

但它们共享同一个上下文实例

也就是说------它们是在同一个空间规则下进行排列的。


何谓"就近支配原则"?

"就近支配"说的是:

盒子总是在"离自己最近的格式化上下文"中参与排版。

也就是说:

  • 如果自己创建了新的上下文,就在自己的里面排;
  • 如果自己没创建,就上交给父辈(最近的那个)去排。

比如:

plain 复制代码
<div class="parent">
  <div class="child"></div>
</div>

如果 .parent 没有触发新 BFC → .child 的排版就"上交"给上层(<html>的 BFC)。

如果 .parent 触发了新 BFC → .child 就在 .parent 的上下文内排,不再上交。


总结对比表
盒子 是否创建新上下文 所属上下文 备注
<html> 自己(根 BFC) 文档根上下文
<div> 默认 继承上层 BFC 共享环境
<div> + overflow:hidden 自己(新 BFC) 独立布局空间
<span> 父级块的行内格式化上下文 IFC 内排版

关键理解:

所谓"每个盒子都有自己的格式化上下文",并不是指它一定创建一个 ,而是指它必然处于某个上下文之内

因此:

  • ✅ 语义一致:每个盒子都在上下文中;
  • ✅ 逻辑一致:但默认情况下,它们共享上层上下文;
  • ✅ 没有矛盾,只是"创建者"和"归属者"不同。

总结
关键点 实际含义
"每个盒子都有自身的格式化上下文" 每个盒子都被某个上下文支配(存在归属)
"默认会共享上下文" 只有触发条件的盒子才会新建上下文,其余都归入上层
"就近支配原则" 排版由最近的上下文控制,不会跨层传导

从代码实现的角度理解格式化上下文

BFC / IFC / FFC 等格式化上下文,本质上是浏览器排版引擎内部创建的"对象实例"------一种可编程的数据结构。

也就是说,它们不是抽象理论 ,而是真正存在于浏览器的渲染树(Render Tree)中,有对应的数据对象、边界区域、布局算法函数


从 HTML → DOM → Render Tree 的生成过程

让我们先回顾渲染的核心流程:

plain 复制代码
HTML
 ↓ 解析
DOM(结构树)
 ↓ + CSS
Render Tree(渲染树)
 ↓
布局(Layout)
 ↓
绘制(Paint)
 ↓
合成(Composite)

👉 在"生成 Render Tree"这一步中,

浏览器会为每个元素创建一个 Layout Object(也叫 Frame、Box、Renderer,具体名称因引擎而异)。

每个 Layout Object 就是一个 盒模型实例

它同时会被挂载到某个格式化上下文对象中。


BFC / IFC 在底层的真实形态

在 Chrome 的 Blink(以及早期 WebKit)中:

  • 每个元素在渲染树中由一个 LayoutObject 表示;
  • 如果这个元素触发了一个新的 BFC,就会额外创建一个 "LayoutBlockFlow" 对象;
  • 该对象有自己的坐标系、布局函数(LayoutBlockFlow::layout());
  • 它内部再管理自己的子对象(这些子对象在此上下文内排版)。

伪代码结构如下:

javascript 复制代码
class FormattingContext {
  constructor(type, parent) {
    this.type = type;          // 'block' | 'inline' | 'flex' | ...
    this.parent = parent;      // 上级上下文
    this.children = [];        // 子盒子(boxes)
  }

  layout() {
    if (this.type === 'block') {
      this.blockLayout();
    } else if (this.type === 'inline') {
      this.inlineLayout();
    }
    // ...
  }

  blockLayout() {
    // 垂直排列子盒子
    let y = 0;
    for (const child of this.children) {
      child.position.y = y;
      y += child.height + child.marginBottom;
    }
  }

  inlineLayout() {
    // 水平排列文字/行内盒
    let x = 0;
    for (const child of this.children) {
      child.position.x = x;
      x += child.width;
    }
  }
}

当浏览器解析:

plain 复制代码
<div class="parent">
  <p>文字1</p>
  <p>文字2</p>
</div>

它大致会生成这样的对象层级:

javascript 复制代码
rootBFC = new FormattingContext('block', null);

parentBox = { display: 'block', context: rootBFC };
p1Box = { display: 'block', context: rootBFC };
p2Box = { display: 'block', context: rootBFC };

rootBFC.children = [ parentBox ];
parentBox.children = [ p1Box, p2Box ];

因为 .parent 没有触发新的 BFC,

所以它与 <p> 元素共享同一个 rootBFC 实例。

也就是说------这些盒子都在同一个 FormattingContext 对象中执行同一套垂直布局逻辑。


当开启新 BFC 时会怎样?

当我们写:

plain 复制代码
.parent {
  overflow: hidden; /* 触发 BFC */
}

浏览器在构建渲染树时就会检测到这个条件,

于是:

javascript 复制代码
rootBFC = new FormattingContext('block', null);
parentBFC = new FormattingContext('block', rootBFC);

parentBox.context = parentBFC;
p1Box.context = parentBFC;
p2Box.context = parentBFC;

rootBFC.children = [ parentBFC ];
parentBFC.children = [ p1Box, p2Box ];

📊 图形化理解:

plain 复制代码
rootBFC
 └── parentBFC
      ├── p1
      └── p2

这样就"物理"地隔离了父外边距的传播路径。

也就是说------margin 塌陷不会跨 BFC 边界发生,

因为它们属于不同的上下文对象实例

浏览器的布局计算函数不会合并它们的边界。


为什么说"共享同一个上下文实例"?

因为如果不触发新的上下文,所有兄弟盒子都:

  • 属于同一个 FormattingContext 对象;
  • 使用同一个 layout() 函数;
  • 共享同一个坐标系(top 从上往下累加)。

因此,

当我们说"共享上下文"时,确实就是在说:

它们在底层共享同一个数据结构实例(一个 FormattingContext 对象)。


对比说明(带类比)
术语 概念层面 浏览器内部 类比现实
盒子(Box) 视觉矩形区域 LayoutObject 一张纸
格式化上下文(FC) 布局规则空间 FormattingContext 对象 一个书写的画布
创建 BFC 独立布局空间 新建一个 FormattingContext 实例 在纸上开一块新区域独立写字
共享 BFC 使用同一上下文 共享同一个 FormattingContext 实例 多张纸在同一画布上写字

总结

格式化上下文(BFC / IFC 等)是真实存在的对象实例,控制盒子的布局规则与坐标空间。

默认所有盒子共享上层上下文的实例,

当某元素触发新 BFC 时,浏览器会新建一个新的上下文对象,

从此内部的盒子在独立的空间中布局。


嵌套初识------元素的两重身份
  • 元素既是「被排版的个体」,又是「排版他人的空间」。
  • 也就是说:
    • 一方面在父亲那里要排好自己
    • 另一方面又要 在自己体内排好孩子
  • 所以看似"创建上下文"与"共享上下文"矛盾,
    其实是因为我们在谈"不同层级的语境"。
    下面我们彻底把这件事讲清楚。

必须区分的两个层面
层面 问题 举例
外层角色 它在父亲的世界里怎么活? div 在父级中是一个块、span 是一个行内盒
内层世界 它自己如何安排孩子? div 容纳块流或行内流,p 只容纳行内流

这两个层面正是前面你提到的:

① 在父级流中如何表现

② 在自己内部创建什么样的流


提前补充:CSS 的"双层 display 机制"

正是因为这种"内外分离"的思考,

后来CSS3 才提出了双层定义:

plain 复制代码
display: <outer> <inner>;

例如:

写法 外层行为(在父中) 内层行为(对子代)
display: block flow 作为块盒出现 创建一个 块级格式化上下文(BFC)
display: inline flow 作为行内盒出现 创建一个 行内格式化上下文(IFC)
display: block flex 作为块盒出现 创建一个 Flex 格式化上下文(FFC)
display: inline grid 作为行内盒出现 创建一个 Grid 格式化上下文(GFC)

几乎所有元素的默认 display 都同时定义了"在父中怎么表现"和"对儿子如何布局"。

后面我们会深入去讲一讲CSS 的"双层 display 机制",这里先有一个概念。


三种基本嵌套
  • 块元素中只能直接形成块流行内流 (什么时候形成块流、什么时候形成行内流,与前面讲行内流时提到的


    内部流差异的问题一样,先耐下心继续往下,逐步深入);

  • 行内元素的外层如果是块 → 它就在行内流中排列;

  • 块中再嵌套块,会产生"新块流";

  • 行内中嵌块,会触发"断流"或"匿名块盒"机制(流在大流里流,但不能反向穿透);


块级元素嵌套块级元素 (允许)

plain 复制代码
<div>
  <p>一段文字</p>
  <section>另一块内容</section>
</div>

逻辑:

外层 <div> 形成一个块流;

内部 <p><section> 作为"块级盒",在垂直方向依次排列。

结果直观:

plain 复制代码
[div]
 ├── [p]
 └── [section]

这一层层结构就像:

  • 一个主水渠里流着多个小浮板;
  • 每个浮板之间有间隙(margin);
  • 不冲突、不破坏主河道的流动。

块中嵌套行内元素 (允许)

plain 复制代码
<p>你好,<span>世界</span>!</p>

逻辑:

  • <p> 创建块流;
  • 块流内部自动形成行内格式化上下文(IFC)
  • 所有文字与 <span> 都在这个 IFC 中水平排列;
  • 一行放不下,就自动换行。

可视化理解:

plain 复制代码
Block Flow(垂直)
└─ Line Box(水平)
   ├─ Inline Box(文字"你好,")
   ├─ Inline Box(<span>世界</span>)
   └─ Inline Box(文字"!")

就像一个大浮板上,放着一排排小积木(行内元素),顺序排开。


行内中嵌套块 (不允许,断流)

plain 复制代码
<p>这是一个段落,<div>我在中间放了个 div</div>看看会怎样?</p>

问题出现了:

  • <p> 本身是块级元素,内部是行内流;
  • <div> 是块级盒;
  • 行内流里不允许出现块级盒,就像一条细水流里突然塞进一块大石头,会"堵住水流"!

浏览器怎么办?

它必须"改造流向"------于是会触发一种"断流机制",在渲染时:

  • <p> 是行内流;
  • <div> 是块流;
  • 行内流中不能直接嵌块 → 浏览器会自动分裂 **<p>**,将其重构 为三个匿名块:
plain 复制代码
<p>这是一个段落,</p>
<div>我在中间放了个 div</div>
<p>看看会怎样?</p>

这就是所谓的 匿名块盒(anonymous block box)机制。

这体现了"文档流的层级自治":每层只能处理自己那一类排版。

通俗理解:

浏览器发现"细流中塞了大石头",就把细流一分为三段:

石头前一段小溪、石头本身、石头后一段小溪,

这样整个水流依然保持"自上而下"的结构。


嵌套规则总结

在文档流中:

每个盒子必须在自己所属的"流体层"中生效,不能越层。

规则 描述 举例
块级盒只能存在于块级流中 块中可以嵌块 <div><p></p></div>
行内盒只能存在于行内流中 块中可以嵌行内 <p><span></span></p>
行内流中不能直接出现块盒 若出现则断流 <p><div></div></p>
每个盒子形成自己的上下文(重点) 格式化上下文彼此独立 BFCIFC

再看一层:为什么这么设计?

CSS 最初就是为"文本文档"设计的,而文本天然有"层次":

  • 段落是大的逻辑块;
  • 段落内部才有文字、图片;
  • 文字中再嵌块,就会破坏"段落结构"。

所以浏览器必须维持"从外到内逐层独立"的格式化原则:

块控制空间分区,行内控制内容排列


盒子的两个身份与格式化上下文
两个身份的表现

块级元素与行内元素,各自会形成各自的格式化上下文,去容纳其子元素,但同时,他们本身作为元素,也会成为其父元素的子元素,那么这两种不同身份,各自应当如何表现呢?

事实上,CSS 中,"元素"有两个"身份":

  1. 它在父级流中如何表现(自己是块盒还是行盒);
  2. 它在自己内部创建什么样的流(子内容如何排版)。
元素 在父级中的角色 自己内部的流 原理
<div> 块级盒(在父中垂直排列) 块级格式化上下文(BFC) 用于容纳更多块
<p> 块级盒(在父中垂直排列) 行内格式化上下文(IFC) 用于排版文字
<ul> 块级盒 BFC(包含 <li> 每个 li 自成块
<li> 块级盒 IFC(文字排列)或 BFC(嵌套列表) 看内容类型
<span> 行内盒 无(行内自身不再生成独立上下文) 它只是 inline box

很多人误以为"块级元素就一定在其内部产生块流",这是个误区。

正确的理解是:块级元素的"块性",只定义了它在父亲那一层的行为,而不是它内部的世界。


具体示例来分清"创建"与"共享"

例 1:div 中嵌套 div

plain 复制代码
<div class="outer">
  <div class="inner"></div>
</div>
元素 外层行为 内层规则 结果
outer 在根 BFC 中垂直排列 内部定义 block flow(BFC) 内部布局规则为 block flow,但仍属于根 BFC
inner 在 outer 的 block flow 中垂直排列 内部同样是 block flow 嵌套不隔离 margin、float 等影响

解释:

inner 处在 outer 的排版空间中,

虽然 inner 自己也"定义了 BFC 规则",

但并没"触发独立 BFC",

它的流与 outer 的是连续的


例 2:p 中嵌文字与 span

plain 复制代码
<p>Hello <span>world</span></p>
元素 外层行为 内层规则 结果
p 块级盒,在根 BFC 中垂直排列 内部是 IFC(行内流) 内容横向排列、文字换行
span 行内盒,在 p 的 IFC 中排列 不创建新流 参与 p 的行盒排版

解释:
p 在外层是块(block),但它的内部不是块流,而是行内流(inline flow),

这就是"块中包行"的层次结构。


例 3:触发独立上下文

plain 复制代码
.outer {
  overflow: hidden; /* 触发独立 BFC */
}

此时:

  • .outer 不再与根 BFC 共享;
  • 它创建一个真正隔离的"排版空间";
  • 子元素 float、margin 不会影响外部。

这才是"独立的格式化上下文(new context)"。


总结核心逻辑图
plain 复制代码
每个元素:
 ├─ 外层身份:作为子参与父的上下文(共享或隔离)
 └─ 内层身份:作为父支配自己的子(定义排版规则)

📘 统一认知:

  • "创建格式化上下文" = 定义内部布局规则;
  • "共享格式化上下文" = 没有触发新的独立空间;
  • 它们并不矛盾,是不同层级的逻辑。

<div><p>内部流差异的问题:第二身份摇摆不定------我是底下的盒子是跟我一起在BFC的这大家庭,还是我成立IFC去管他们?
谁决定"内部流类型"?

这是浏览器渲染排版的一个底层算法:

每个盒子在渲染时会经历:

  1. 确定自己的外部表现形式(display 的外层部分);
  2. 根据内部内容类型创建对应的"格式化上下文"(Formatting Context)。

这个过程由**HTML层的内容语义模型(Content Model)CSS层的布局模型(Display Model)**共同决定。


display 的双层模型

这里我们再次来看display 的双层模型CSS3 才正式明确语法,但其理念自CSS1就确定并应用)这个概念,方便我们理解CSS布局模型:

其实 display 包含两层含义:

plain 复制代码
display: <outer> <inner>;

| 外层(outer) | 控制它在父级中如何参与排版 | 例如 block / inline / list-item |

| 内层(inner) | 控制它内部内容如何排版 | 例如 flow / flex / grid / ruby |

完整写法 含义
display: block flow; 外层表现为块级,内部使用普通文档流排版(flow layout)
display: inline flow; 外层表现为行内盒,内部也是普通流
display: block flex; 外层为块级,内部为弹性布局
display: block grid; 外层为块级,内部为网格布局

其中:

flow 就代表传统的"普通文档流布局(Normal Flow)"。

它包含两种内部上下文:

  • 块格式化上下文(BFC,Block Formatting Context)
  • 行内格式化上下文(IFC,Inline Formatting Context)

二者的CSS布局模型
plain 复制代码
div { display: block flow; }
p   { display: block flow; }

对于{ display: block flow; }浏览器的算法规则是:

若块盒内部内容为文本或行内级元素 → 创建 IFC;

若块盒内部内容为块级元素 → 创建 BFC。

  • <div> 虽然是块级,但它里面是块(div/p等),所以内部形成块流;
  • <p> 虽然是块级,但它里面是文字(行内),所以内部形成行内流。

但注意,只是初步解释,因为我们前面说是**HTML层的内容语义模型(Content Model) CSS层的布局模型(Display Model)**二者共同决定内部流类型,我们现在只看了CSS层的布局模型,二者都是{ display: block flow; },也就是说内层是默认文档流,会根据内容是什么自行决定变成BFC还是IFC,但为什么明明都是flow,

的内部却不允许放块级元素呢?


CSS 只是"表现层",而 HTML 决定了"能不能这么做"

CSS 的 display 属性负责描述"如何展示";

而 HTML 的标签本身,决定"能包含什么内容"。

这两者的关系是:

层级 谁控制 控制什么
HTML 层 内容语义模型(Content Model) 哪些标签可以嵌哪些标签
CSS 层 布局模型(Display Model) 嵌进去后,怎么排版

举例:

plain 复制代码
<p>这是段落 <span>行内</span>。</p>   ✅ 合法
<p>这是段落 <div>块级</div>。</p>     ❌ 非法
  • CSS 的角度pdisplay: block flow;,理论上内部可以排版块或行内。
  • 但从 HTML 的角度p 的内容模型(content model)规定:

"p 只能包含_短语内容(phrasing content)_,即行内内容。"

这就是标准强制的规则

<p> 元素不是结构容器,而是语义化的文本段落

它代表一个行内排版区域(IFC 容器),内部不允许出现块级元素

因此:

  • <div>:结构性容器,无语义限制,能容纳块或行内;
  • <p>:语义性容器,HTML 层面限制只能放文字或行内标签。

👉 ** 问题的根源不是 CSS display,而是 HTML 语义规范。
**<p>** 不能容纳块,是"语义限制",不是"排版能力不足"。 **


那为什么 <div> 没有限制?

因为 <div> 的语义是:

"通用分区容器(Generic Block Container)"

即:它没有自己的语义,只负责结构。

因此浏览器在构建渲染树时:

  • 发现 <div> 是块级;
  • 发现内部是什么都行;
  • 就直接根据内部内容决定上下文类型。
<div> 内部内容 形成的上下文 原理
文本 / 行内元素 行内格式化上下文(IFC) 像段落一样换行排版
块级元素 块格式化上下文(BFC) 垂直排列

所以 <div> 是个"中立容器",能容纳任何类型的流。


从HTML、CSS、浏览器"职责分离"的角度再看
层次 <div> <p> 说明
HTML 内容模型 任意内容(块/行内) 仅短语内容(行内) 决定嵌套合法性
CSS display block flow block flow 排版潜能一致
浏览器渲染时的行为 根据内容生成 BFC 或 IFC 强制创建 IFC,仅允许行内流 实际呈现不同

HTML 决定能不能这么排,CSS 决定排出来是什么样,浏览器负责解读两者并呈现。


总结
  • <div> 是块级容器,内容是块流;
  • <p> 是块级段落,内容是行内流;

它们都"是块",但职责不同:

前者管理空间(外层结构),后者管理文字(内层排版)。

想象网页是一个办公大楼:

元素 角色 含义
<div> 通用办公楼 你可以在里面放办公室、仓库、会议室(块),或办公桌(行内)
<p> 专用办公区(写字间) 只能放办公桌(文字),不能再盖会议室(块)

所以:

  • div 是"可扩展楼层";
  • p 是"专用文字层"。

BFC 与 IFC 的层级关系与触发机制
前置思考

对于:<div><p>Hello <span>world</span></p></div>

div共享了顶层的BFC,p也共享了顶层的BFC,但是p的内部,是IFC,这里的IFC是不是新的独立的格式化上下文?这里没有涉及到overflow: hidden;这样的触发独立上下文的语句

先看结构与默认上下文

HTML:

plain 复制代码
<div>
  <p>Hello <span>world</span></p>
</div>

默认情况下,这个结构中存在以下层级:

元素 格式化上下文类型 是否独立
<div> BFC(Block Formatting Context,块级格式化上下文) ❌ 不独立(延用顶层)
<p> 参与父级 BFC 的一个块级盒子 ❌ 不独立
<p> 内部的文字与 <span> IFC(Inline Formatting Context,行内格式化上下文) ✅ 独立(嵌套于 BFC 中)

关键结论:IFC 是新上下文,但不是"块级意义上的独立上下文"

我们思考的重点是:

"p 的内部是 IFC,这是不是新的独立的格式化上下文?明明没触发 overflow:hidden 等属性呀?"

👉 答案是:是的,它是新的格式化上下文(IFC),但它和 BFC 属于不同类型的上下文。

  • BFC (Block Formatting Context)负责块级盒子之间的排列与外边距折叠等;
  • IFC (Inline Formatting Context)负责行内盒子(文字、inline元素)之间的排版、行盒计算。

两者之间不是并列关系,而是嵌套关系

一个块级盒子(例如 <p>)在参与父级的 BFC 布局时,其内容区域内部会自动生成一个 IFC,用于布局文本与行内元素。


IFC 是如何自动产生的?

触发 IFC 不需要写 overflow: hiddendisplay: inline-block 等属性。

触发条件非常简单:

当一个盒子包含行内级内容(文字、<span><a> 等),则会自动创建一个"行内格式化上下文(IFC)"来排列这些内容。

也就是说:

plain 复制代码
<p>Hello <span>world</span></p>

中,<p> 内部自动进入了 IFC 模式。

这时:

  • "Hello " 是一个匿名行内盒;
  • <span> 是一个行内盒;
  • 它们一起被 IFC 管理,形成"行盒(line box)";
  • <p> 的高度由这些行盒决定。

总结:BFC 与 IFC 的嵌套关系(类比图)

可以这么想:

plain 复制代码
┌────────────────────┐
│ div(BFC 容器)    │ ← 管理块级盒子排列
│ ┌────────────────┐ │
│ │ p(块盒)       │ │ ← 参与 div 的 BFC
│ │ ┌────────────┐ │ │
│ │ │ Hello <span> │ │ │ ← p 内自动形成 IFC(行内格式化上下文)
│ │ └────────────┘ │ │
│ └────────────────┘ │
└────────────────────┘

所以:

  • <div> 创建了一个 BFC
  • <p> 是这个 BFC 的子块;
  • <p> 的内部自动形成 IFC,独立负责内部的文字与行内布局;
  • 这两个上下文独立负责不同维度的排列,不相互干扰。

为什么这种嵌套很重要?

它解释了许多 CSS 现象,例如:

  1. 文本排版不受外层 margin 折叠影响
    因为文字处于 IFC 内部,而 margin 折叠只在 BFC 层级之间发生。
  2. **vertical-align**** 只能在 IFC 内起作用**
    因为它是行内盒子的相对对齐,而 BFC 不理解 vertical-align。
  3. 行高(line-height)计算在 IFC 中进行
    因为每个行盒的高度由 IFC 决定,而不是 BFC。

具体我们会在下面逐个深入。


简要总结
概念 全称 管理对象 触发条件 是否独立上下文
BFC Block Formatting Context 块级盒子之间的布局 floatoverflowdisplay: flow-root
IFC Inline Formatting Context 行内元素、文本的布局 盒子中出现行内级内容 ✅(独立于 BFC 内部)

BFC 管"块的排列",IFC 管"字的排列"。

块中有字,就会自动生出 IFC;

字的世界不需 overflow:hidden 才独立。


BFC与IFC之间的差异
总体认知先行:块与字的"两界之分"

在 CSS 的视觉格式化模型中,所有元素都要落入某种"格式化上下文(Formatting Context)"中:

  • 块格式化上下文(BFC) :管理块级盒子的布局(上下排列、margin 折叠、浮动避让......)
  • 行内格式化上下文(IFC):管理行内盒子(文字、inline 元素)的布局(行盒高度、基线、vertical-align......)

一句话概括:

BFC 决定"段落与段落"如何排,

IFC 决定"字与字"如何排。


文本排版不受外层 margin 折叠影响

因为文字处于 IFC 内部,而 margin 折叠只在 BFC 层级之间发生。


1. 问题场景

看这样一段代码:

plain 复制代码
<div>
  <p>Hello</p>
  <p>World</p>
</div>

默认情况下:

  • <div> 创建了一个 BFC;
  • <p> 是块级盒,参与这个 BFC;
  • 每个 <p> 自身内部又是一个 IFC,用来排字。

2. Margin 折叠发生在哪里?

margin 折叠(Margin Collapsing) 的特征是:

只发生在"块级盒子"之间的 垂直方向

比如:

  • 相邻两个块(pp)之间的 margin-bottommargin-top 会折叠;
  • 块的第一个子块的 margin-top 会与父块的 margin-top 折叠。

但注意:

  • 行内元素(inline)没有 margin 折叠机制
  • IFC 内部的所有计算,都不参与 BFC 的 margin 折叠规则

3. 为什么文字不会被折叠?

因为文字、<span> 等都处在 IFC 的行盒体系中。

  • IFC 管理的是行盒(line box) ,它们的上下间距由 line-height 和字体盒模型决定;
  • 这些行盒并不是"块级盒",所以 margin 折叠规则根本不会触发。

4. 结论

Margin 折叠 是块与块之间的"垂直世界"规则;
文字布局 属于 IFC 的"水平世界";

两个世界的规则互不干扰。

所以文字(IFC 内)永远不受外层 BFC 的 margin 折叠影响。


vertical-align 只能在 IFC 内起作用

因为它是行内盒子的相对对齐,而 BFC 不理解 vertical-align。


1. 问题场景

plain 复制代码
<p>
  文本 <img src="..." alt="" style="vertical-align: middle;">
</p>

我们用 vertical-align: middle 想让图片与文字垂直居中。

但如果你写在一个块上,比如:

plain 复制代码
div {
  vertical-align: middle;
}

就完全无效。为什么?


2. 关键原因:vertical-align 属于 IFC 的语义

vertical-align 的语义是:

控制"行内盒"在当前 行盒(line box) 中相对于基线(baseline)的垂直对齐方式。

换句话说:

  • 它只存在于 IFC 内的行盒层面
  • 每个行内元素都会被放在某个行盒中,CSS 会根据 baseline、上升线(ascent)、下降线(descent)等字体度量值计算垂直对齐。

BFC(块级世界)中没有这些概念,它的垂直布局由 margin、border、padding、height 等决定。

因此 BFC 根本"不理解 vertical-align 这句话的语义"。


3. 结论

vertical-align 是 IFC 的语言,BFC 听不懂。

它作用于行盒内的元素,而非块级元素。


行高(line-height)计算在 IFC 中进行

因为每个行盒的高度由 IFC 决定,而不是 BFC。


1. 关键点:行高的本质是什么?

当一个块级盒(如 <p>)中有行内内容时,浏览器会为它创建 IFC,并在其中生成一系列 行盒(line box)

每一行的高度(line box height)就是我们常说的 line-height 的体现。


2. 行盒是怎么计算的?

行盒高度来源于每个行内盒的尺寸。

例如:

  • 文本节点 → 字体的 ascent + descent
  • <img> → 图片高度
  • <span style="font-size:2em"> → 放大后的字体度量

浏览器会取这些值的"最大上升线 + 最大下降线"作为行盒高度。

line-height 会用来调整行盒的上下间距。

当多个行盒垂直堆叠时,line-height 决定它们的距离。


3. 为什么不由 BFC 管?

因为:

  • BFC 管的是块与块的距离;
  • IFC 内的行盒是"块内的内部细节",由 IFC 自己排布;
  • 所以 BFC 只看到 <p> 整体的高度,不参与行高计算。

4. 一句话总结

行高是 IFC 内部的"微观排版属性",

BFC 只关心块整体的"宏观空间尺寸"。


BFC 与 IFC 的分工协作
领域 参与对象 控制内容 代表属性 折叠或对齐规则
BFC(块世界) 块级盒子(div , p , section 上下排列、margin 折叠、float 清除 margin , overflow , display , position margin 折叠
IFC(字世界) 行内盒子(文字、span , img 行内排版、基线对齐、行高控制 line-height , vertical-align 无折叠,按基线对齐

BFC 决定"块之间怎么排";

IFC 决定"块里的字怎么排"。

Margin 折叠是块世界的规则,

Vertical-align 与 Line-height 是字世界的语言。


display属性发展史

我们今天习惯写的 display:flex;display:block;display:inline-block; 等,其实是对 CSS 两层模型的一种"简化封装"。

要真正理解这一点,我们得从历史演化角度来讲起。


CSS1 时代(1996):display 只有"外层含义"

当年,CSS 还非常简单,网页的结构也非常单一。

当时的目标

------只是让"块"和"行"排得整齐。

于是,display 的设计也非常朴素,只描述了外层的排布方式(outer display type),即:

plain 复制代码
display: block;   /* 独占一行,垂直排列 */
display: inline;  /* 与文字一起排,水平排列 */
display: none;    /* 不显示 */

这时的"内层内容"根本没被考虑到,因为:

  • 那时的网页内容主要是文字;
  • <p> 里的文字都是纯文本,不存在"复杂子结构";
  • 没有表格、没有浮动、没有定位。

所以早期的浏览器默认认为:

"块元素内部就是一堆行(inline context)。"

因此:

plain 复制代码
<p>文字文字文字</p>

👉 <p> 自己是"块流(block)",

但它的内部内容是"行内流(inline)"。

这就是为什么:

p 是块级元素,却只能排行内内容。


CSS2 时代(1998):引入"双层 display 模型"

网页变复杂了,人们开始需要控制表格、浮动、列表、弹性排布。

W3C 于是重新定义了 display

display = <outer-display> + <inner-display>

即:

plain 复制代码
display: block flow;
display: inline flow;
display: block flex;
display: block grid;
display: inline flex;

但问题是:
CSS2 时期的语法规范中,并没有支持写两部分!

所以虽然在规范内部是分开的(逻辑上),但写法上仍然只能写一个关键词,浏览器会自动推导出另一半。

例如:

写法 实际对应的两层
display: block; block flow
display: inline; inline flow
display: flex; block flex
display: inline-flex; inline flex
display: grid; block grid
display: inline-grid; inline grid
display: table; block table

也就是说:

你写一个词,浏览器会自动"推断出"另一层含义。


为什么要分两层?

这是因为------CSS 世界是嵌套的。

每个盒子既有外在行为 ,又有内部规律

层级 控制对象 举例 类比
外层 display 元素在外部文档流中怎么"占位" block / inline 一个人"在社会上的身份"
内层 display 元素内部的内容怎么排版 flow / flex / grid 一个人"在家里的生活方式"

举例:

plain 复制代码
display: block flex;

表示:

  • 这个盒子在外面表现为一个"块"(独占一行);
  • 但它内部用"弹性布局"排列子项。

为什么 CSS 只写一个值?

因为:

  1. 历史兼容性------早期浏览器(CSS1、CSS2)根本不识别双值;
  2. 用户友好性------写一个词就够了,浏览器帮你补;
  3. 简洁直观------大多数开发者并不关心内外层的细节;
  4. 可扩展性------CSS3 之后,标准仍保留了双层结构,只是"语法层面上简化"。

在现代 CSS 中,双层写法重新被正式支持

CSS Display Level 3(2020s)终于允许我们显式写双值

plain 复制代码
display: block flow;
display: block flex;
display: inline flow-root;
display: block grid;

这样写是完全合法的。

而且还引入了新的内层类型:

  • flow-root:开启新的块格式化上下文(相当于 overflow:hidden hack)
  • contents:让元素本身消失,只保留子元素结构
  • table / ruby:用于表格或注音排版

演化对照表

时代 写法 语义层数 浏览器处理逻辑 举例
CSS1 display:block; 单层(outer) 默认内层为 flow 简单文字排版
CSS2 display:block; 双层(outer+inner) 隐式推断 inner 块流中嵌行流
CSS3 display:flex; 双层(outer+inner) outer=block, inner=flex 弹性盒布局
CSS4+ display:block flex; 双层(显式) 明确两层含义 可定制排版模型

补充:display: inline-block 的双层结构与含义

先看它的标准定义:

plain 复制代码
display: inline-block;

这是一个复合 display 类型,展开后其实是:

plain 复制代码
display: inline flow-root;

outer display:inline

表示这个盒子在外部的行为方式。

  • 像文字一样 在行内排布;
  • 会与文字或其他 inline 元素在同一行中并排出现
  • 受到基线(baseline)对齐的影响。

换句话说,它不是独占一行,而是一个"能进文字队列"的盒子。


inner display:flow-root

表示它内部的内容如何排布。

  • 它内部是一个独立的块级格式化上下文(BFC)
  • 可以在内部放任何块元素;
  • 内部不会与外部流相互影响(不会被浮动塌陷或 margin 合并干扰)。

因此:

🔸 它"在外面看起来像个字",

🔸 但"里面却是一个小的独立世界,可以正常排版块内容"。


小结
层级 含义
outer display inline 像文字一样并排
inner display flow-root 内部自成一格,像小型 div

所以 inline-block 就像一个微型独立排版岛

"能插入句子里的一整个小盒子。"

这实际上就解决了我们前面提到的行内流中不能插入块流的问题!


举例说明:

plain 复制代码
文字 <div style="display:inline-block;">A<br>B<br>C</div> 文字

视觉效果:

plain 复制代码
      A
      B
文字	C  文字

说明:

  • 这个 div 在外部被当作文字(inline);
  • 但内部有多行块内容(flow-root),自己排自己的,不影响外层。

继文档流后的发展

脱离文档流:浮动与定位的突破

文档流虽然稳定,但有时太"死板"。

于是后来人类为它开辟了逃脱机制:

技术 意义 结果
float 元素"漂浮"在流之上 文本环绕效果
position: absolute 元素脱离父流,独立定位 精准放置
position: fixed 相对视口固定 悬浮导航等
flex / grid 建立新的流(新格式化上下文) 容器级布局

它们都在"改变文档流的力量分配方式"。


文档流的物理隐喻

可以这样理解整个排版宇宙:

隐喻 意义
"文档流" 是引力场 决定元素自然落地的位置
"浮动 / 定位" 是外力 改变元素的自然轨迹
"格式化上下文" 是局部时空 控制该区域内的物理规则
"Flex / Grid" 是新文明 重新定义流的维度与秩序

从"流"理解一切布局

1️⃣ 当你看一个布局,不要问"元素为什么在那里",

而要问"它是在哪个流中?"

2️⃣ 判断规则:

  • 它是否在文档流中?
  • 它创建了新的格式化上下文吗?
  • 它如何与父流交互?

3️⃣ 真正掌握 CSS 的关键是:
学会"观察流动"而不是"记忆属性"。


核心总结

概念 定义 控制方向 是否脱离流
文档流(Normal Flow) 默认的排版机制 上下 + 左右
BFC(块格式化上下文) 块级排列环境 垂直流
IFC(行内格式化上下文) 行内排列环境 水平流
相关推荐
用户47949283569153 小时前
面试官:讲讲这段react代码的输出(踩坑)
前端·javascript·react.js
jump6803 小时前
闭包详细解析
前端
观默3 小时前
AI看完你的微信,发现了些秘密?
前端·开源
林希_Rachel_傻希希3 小时前
《DOM元素获取全攻略:为什么 querySelectorAll() 拿不到新元素?一文讲透动态与静态集合》
前端·javascript
PHP武器库3 小时前
从零到一:用 Vue 打造一个零依赖、插件化的 JS 库
前端·javascript·vue.js
温宇飞3 小时前
CSS 内联布局详解
前端
excel4 小时前
深入理解 Slot(插槽)
前端·javascript·vue.js
GISer_Jing4 小时前
React中Element、Fiber、createElement和Component关系
前端·react.js·前端框架
折翼的恶魔5 小时前
前端学习之样式设计
前端·css·学习