←返回笔记列表
01 · NOTES / WebGIS 开发

第 05 节 · 盒模型与布局流

2026 年 9 月 7 日

第 05 节 · 盒模型与布局流

📌 版本信息:基于 CSS 现行规范(box-sizing、常规流均为稳定特性;2026-09-06 核对) 📚 来源:MDN 盒模型 | MDN 常规流 | MDN box-sizing

当卡片说自己 300px 却占了 350px

第 04 节把选择器学完后,第一件想做的事往往是把城市卡片摆整齐。结果很快就遇到怪事:width: 300px 的卡片,加上内边距和边框后,实际占了 350px;卡片间距一会儿大一会儿小,明明只写了 margin,整块内容却被莫名顶开。

这些现象不是浏览器发疯,而是“盒模型”的规则在起作用。每个元素从内到外都有自己的内容区、内边距、边框和外边距,只有把这些层弄清楚,宽度和间距才可控。这一节就从盒模型入手,把文档流的默认行为和两个 margin 经典现象一次讲透。

这一节讲什么

本节讲盒模型的四层结构、box-sizing、块级与行内元素的行为差异、常规流,以及 margin 合并和传递。学完这一节可以做到:

  • 说出 content、padding、border、margin 各管什么,并能读写简写值
  • 解释 width 在 content-box 和 border-box 下的差别,统一使用后者
  • 分清块级、行内、行内块,知道为什么行内元素设宽高无效
  • 解释 margin 合并和传递的原因,并给出可复用的解决思路
  • 用 DevTools 的盒模型图快速定位间距问题

一切元素都是盒子

浏览器会把每个元素画成一个矩形盒子,从内到外四层:

┌─────────────────────────────────────┐
│  margin(外边距:盒子与邻居的距离)      │
│  ┌───────────────────────────────┐  │
│  │  border(边框)                 │  │
│  │  ┌─────────────────────────┐  │  │
│  │  │  padding(内边距:          │  │  │
│  │  │  内容与边框之间的呼吸空间)   │  │  │
│  │  │   ┌───────────────────┐  │  │  │
│  │  │   │  content(内容)    │  │  │  │
│  │  │   └───────────────────┘  │  │  │
│  │  └─────────────────────────┘  │  │
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘

类比寄快递:content 是商品,padding 是泡沫,border 是纸箱壁,margin 是快递箱之间的空隙。背景色会覆盖 content 和 padding 区域,但不会延伸到 margin;边框画在两者之间。

padding、border、margin 的写法

.card {
  width: 300px;                 /* 默认 content-box 下,它先管内容区宽度 */
  padding: 16px;                /* 四边相同 */
  padding: 8px 16px;            /* 上下 8,左右 16 */
  padding: 8px 16px 24px;       /* 上,左右,下 */
  padding: 8px 16px 24px 32px;  /* 上 右 下 左,顺时针 */
  border: 1px solid #ddd;       /* 粗细 样式 颜色 */
  margin: 0 auto 24px;          /* 上,左右 auto,下;auto 负责水平居中 */
}

margin: 0 auto 是水平居中块级元素的经典写法:左右外边距自动均分剩余空间。行内元素不适用这个方法,垂直方向的 auto 也不会像水平方向那样自动分配。

box-sizing:宽度到底算到哪

默认盒模型叫 content-box,它有个反直觉的坑:width 只算内容区。

.box {
  width: 300px;
  padding: 20px;
  border: 5px solid;
  /* 实际占宽 = 300 + 20×2 + 5×2 = 350px */
}

写 width: 300px 时心里想的是“这个盒子占 300px”,浏览器实际却给了 350px。多列布局里每一列都偷偷多出几十像素,结果就是溢出和换行。

行业解法是全局切换成 border-box,让 width 直接算到边框为止:

*,
*::before,
*::after {
  box-sizing: border-box;
}

这条重置是现代项目的标配。从今天起,新项目 CSS 的第一段就写它;看到别人项目里有这一段时,也能明白它是在固定“说 300 就是 300”的语义。

块级、行内、行内块

类型 典型标签 排版行为
块级 block div、p、h1、section、ul 独占一行,默认宽度撑满父容器,宽高和垂直 margin/padding 都有效
行内 inline span、a、strong、img 在文字流里排成一行,宽高由内容决定,直接设宽高无效
行内块 inline-block 用 display 改造 既能排成一行,又能设宽高
span { display: block; }                  /* 行内 → 块级 */
li { display: inline-block; }             /* 列表横排 */
a { display: inline-block; padding: 8px 16px; }  /* 给链接可点击的“肉身” */

行内元素不是“不能有大小”,而是它的大小由内容决定,直接写 width 不会生效。想给链接做按钮、给文字块做标签,就用 inline-block 或后面的 Flex。

文档流与两个经典现象

文档流(normal flow)指不做任何特殊布局时的默认排版:块级从上到下堆叠,行内从左到右排列。两个“看起来像 bug”的现象都来自这里的规则:

margin 合并:垂直方向上,相邻两个块级盒子的 margin 取较大值,而不是相加。

<p style="margin-bottom: 30px">上段</p>
<p style="margin-top: 20px">下段</p>
<!-- 最终间距 30px,不是 50px -->

margin 传递:子元素的 margin-top 在没有 border/padding 隔断时,可能“穿透”父元素,把父元素一起往下推。练习里如果把标题放到卡片第一项,又没有 border/padding 隔断,标题的默认 margin-top 就会把整张卡片顶开,让卡片间距忽大忽小;把它归零后,间距才完全由卡片自己的 margin-bottom 控制。

破解思路是让间距归属明确:要么给父元素加 border/padding 形成隔断,要么直接用 Flex 的 gap。第 06 节学了 Flex 之后,这类 margin 对碰会大幅减少。

用 DevTools 看盒模型

按F12打开 DevTools,选中元素后,Styles 面板底部有一张彩色盒模型图:

  • 蓝色 = content
  • 绿色 = padding
  • 黄色 = border
  • 橙色 = margin

间距对不上时先看这张图,能直接看出是哪一层多了。排查布局时还可以临时给可疑元素加 outline: 1px solid red;,看清它的真实边界。

易错点

  • 忘记 border-box:宽度“说到做不到”,多列布局莫名溢出。新项目第一段 CSS 就是全站重置。
  • 把 width 当总宽度:在 content-box 下,实际占宽还要加 padding 和 border;DevTools 里看到的数字永远比直觉大。
  • 给行内元素设宽高:span 写 width: 200px 不生效,先确认 display 是不是 inline。
  • 用 margin 管理卡片间距:相邻 margin 会合并,写出来的间距和数字对不上。能用 gap 或 padding 的地方优先用它们。
  • 忽略 margin 传递:子元素 margin-top 顶开父元素时,先检查父元素有没有 border/padding 隔断,而不是急着写负 margin。

动手练习:05 卡片布局

配套文件:02-web-basics/examples/05-卡片布局.html。图片已经换成 examples/assets/ 下的本地 SVG 占位图。

先别急着改,打开后观察三个现象:卡片内容贴边、三张卡片挤在一起、图片与标题之间以及标题与描述之间各有一截说不清的间距。再打开 DevTools 选中卡片看盒模型图,然后按文件里的 6 个 TODO 依次修。这次 demo 刻意保留“素坯”状态:所有 TODO 都空着,没有预填答案,每修完一个刷新对比,都应该有肉眼可见的变化。

参考答案:

/* TODO 1:全站统一 border-box */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* TODO 2:让文字不贴边 */
.card {
  padding: 16px;

/* TODO 3:卡片之间拉开 */
  margin-bottom: 16px;

/* TODO 4:卡片水平居中 */
  margin-left: auto;
  margin-right: auto;
}

/* TODO 5:去掉 h3 默认上下外边距的“幽灵间距” */
.card h3 { margin: 0 0 8px; }

/* TODO 6:图片下方留白 */
  margin-bottom: 12px; 

TODO 5 值得单独停下来理解:h3 默认自带上下 margin,会让图片和标题、标题和描述之间出现“看起来对不上”的间距。这里卡片已有 padding 和 border,所以它是卡片内部的间距;如果标题是第一项且父级没有 border/padding 隔断,这个 margin-top 还会穿透到卡片外,把整张卡片顶开。把上下 margin 归零后,卡片间距就完全由卡片自己的 margin-bottom 控制,数值才可预期。

通关标准:

  • 卡片实际宽度等于设定宽度,border-box 生效(用 DevTools 的盒模型图确认总宽是 300px)
  • 三张卡片水平居中、间距一致,文字和图片都不贴边
  • 能指着 DevTools 盒模型图,说出每一层的颜色和数值
  • 能解释 TODO 5 修的是 margin 传递,而不是简单“删掉一个间距”

自测题

  1. 盒模型四层从内到外依次是什么?哪层会显示元素自身背景?
  2. 默认 content-box 下,width: 200px; padding: 10px; border: 2px 的盒子实际占多宽?
  3. 为什么全项目要设 box-sizing: border-box?
  4. <span> 设 width: 200px 无效,为什么?怎么让它生效?
  5. 相邻两个元素的 margin-bottom: 30px 和 margin-top: 20px,最终间距多少?

参考答案

  1. content → padding → border → margin;padding 区域会显示元素自身背景。
  2. 200 + 10×2 + 2×2 = 224px。
  3. 让 width 语义稳定,说到即所得,布局不会因为 padding/border 意外膨胀。
  4. span 是行内元素,宽高由内容决定;改成 display: inline-block 或 block 后生效。
  5. 30px。垂直相邻 margin 合并取大,不相加。

下一步

单个盒子能控制了,但“一堆盒子怎么排”还是靠自然堆叠。第 06 节学 Flex 弹性布局,用一根主轴和一根交叉轴把导航栏、卡片墙、地图侧栏全部管起来。

TAGSweb