首页 CSSDAY3 Box model And Box sizing
文章
取消

CSSDAY3 Box model And Box sizing

开始根据林想的Web工坊大佬的建议路线学习CSS。

本文涉及内容会如下:

Introduction to the CSS box model - CSS | MDN

Understanding and setting aspect ratios - CSS | MDN


Introduction to the CSS box model(CSS 盒模型入门)

浏览器渲染引擎在排版文档时,把每一个元素都表示为一个矩形盒子(rectangular box)。CSS 决定这些盒子的尺寸(size)、位置(position)和外观属性(颜色、背景、边框粗细等)。

每个盒子由内到外包含四个区域,各自有一条边界(edge):

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──────────────────────────────────┐
│         margin area              │  ← margin edge(外边距边界)
│   ┌──────────────────────────┐   │
│   │      border area         │   │  ← border edge(边框边界)
│   │   ┌──────────────────┐   │   │
│   │   │   padding area   │   │   │  ← padding edge(内边距边界)
│   │   │   ┌──────────┐   │   │   │
│   │   │   │ content  │   │   │   │  ← content edge(内容边界)
│   │   │   │  area    │   │   │   │
│   │   │   └──────────┘   │   │   │
│   │   └──────────────────┘   │   │
│   └──────────────────────────┘   │
└──────────────────────────────────┘

整个页面就是一堆盒子摞起来的。

Content area(内容区)

内容区紧贴 content edge 内侧,放元素的”真正的”内容,例如文字、图片、视频播放器等。其尺寸称为 content-box width / content-box height,通常有背景色或背景图。

box-sizing 为默认值 content-box 且元素为 block 元素时,可以用这些属性显式定义内容区大小:

widthmin-widthmax-widthheightmin-heightmax-height

Padding area(内边距区)

padding 区在 content area 外面,由 padding edge 包围。尺寸称为 padding-box width / padding-box height。padding 的粗细由以下属性控制:

padding-toppadding-rightpadding-bottompadding-left,以及简写属性 padding

通俗理解:padding 是内容到边框之间的”填充物”,让内容和边框别贴太紧。

Border area(边框区)

border 区在 padding area 外面,由 border edge 包围。尺寸称为 border-box width / border-box height。边框粗细由以下属性控制:

border-width 以及简写属性 border

关键点一:box-sizing: border-box

当设为 border-box 时,width/height 定义的是 border area 的尺寸(即包含了 padding 和 border),而不是 content area 的尺寸。这就是为什么前端工程里几乎所有项目都设 box-sizing: border-box——元素总宽等于你设的 width,不用手动加减 padding 和 border。

width 定义的是最终元素总宽
content-box(默认)content area 宽度width + padding + border
border-boxborder area 宽度(含 padding + border)width(不用再加)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* content-box */
.box-content {
  box-sizing: content-box;
  width: 300px;
  padding: 20px;
  border: 5px solid;
  /* 实际占用:300 + 20*2 + 5*2 = 350px */
}

/* border-box */
.box-border {
  box-sizing: border-box;
  width: 300px;
  padding: 20px;
  border: 5px solid;
  /* 实际占用:正好 300px */
}

几乎所有 reset CSS / normalize.css 的第一件事就是把所有元素设为 border-box

关键点二:背景延伸到哪

默认情况下,背景(background-color / background-image)会延伸到 border 的外边缘(在 z 轴上延伸到下方)。可以用 background-clip 属性改变这个行为。

Margin area(外边距区)

margin 区在 border area 外面,由 margin edge 包围。尺寸称为 margin box width / margin box height。这是一个空白区域,用来把元素和邻居隔开。大小由以下属性控制:

margin-topmargin-rightmargin-bottommargin-left,以及简写属性 margin

⚠️ margin collapsing(外边距折叠)

当上下相邻的两个块级元素的 margin 相遇时,它们会”折叠”,不是相加,而是取最大值。此时 margin area 没有明确边界,因为 margin 被两个盒子共享了。

CSS 规范只定义了 block direction(垂直方向)的 margin collapsing,水平方向并未设计这个规则。

原因在于 CSS1 时代的设计假设:

1
2
3
4
5
6
7
垂直:block 元素默认堆叠排列
    段落间距天然靠 margin-bottom + margin-top
    如果两个 20px 叠出 40px,段落间距太宽 → 需要折叠

水平:inline 元素默认排成一行
    间距用 margin-left/right 控制,每个元素的间距是独立的
    没有"两个 box 相邻 margin 打架"的问题 → 不需要折叠

而 Flexbox 更进一步,连垂直方向的折叠也不发生了。

margin collapsing 只在正常流(normal flow)的 block 元素之间生效。Flex 容器里的子项(flex items)虽然看起来也是”盒子排在一起”,但它们的布局模型已经不是 normal flow 了,margin 完全独立,上下左右各管各的。

行内非替换元素的特殊行为

对于 non-replaced inline elements(如 <span><a>):

  • 边框和内边距仍然显示在内容周围
  • 但元素在行高上的占位line-height 决定,而不是由 border/padding 撑开

一个 <span> 设了 padding: 20px,上下 padding 视觉上有,但不撑开行高,可能会盖住上下行的文字。

四个区域速查

区域边界尺寸名称核心 CSS
Contentcontent edgecontent-box width/heightwidth, height
Paddingpadding edgepadding-box width/heightpadding
Borderborder edgeborder-box width/heightborder, border-width
Marginmargin edgemargin box width/heightmargin

盒模型 = content → padding → border → margin,从内到外四层。默认 content-boxwidth 只管内容区,border-boxwidth 管到 border 区。

前端开发中统一用 border-box 是常识,减少了很多心智负担。


Mastering margin collapsing(margin 折叠详解)

块级元素的 margin-topmargin-bottom 有时会合并(折叠,collapse)为单个 margin,其大小等于各 margin 中最大者(相等时取其中一个),这就是 margin collapsing。

浮动元素和绝对定位元素的 margin 永远不会折叠。

margin 折叠只发生在垂直方向

margin 折叠发生在三种基本情形中:

Adjacent siblings(相邻兄弟元素)

相邻兄弟元素的 margin 会折叠,除非后一个兄弟需要 clear 清除浮动。

1
2
<p style="margin-bottom: 30px;">上面的段落,margin-bottom 30px</p>
<p style="margin-top: 20px;">下面的段落,margin-top 20px</p>

两个段落之间的间距是 max(30px, 20px) = 30px,而不是 30 + 20 = 50px

No content separating parent and descendants(父子间无隔离物)

父块级元素与其后代元素之间的垂直 margin 也会折叠,前提是它们之间没有隔离物。具体分为两种情况:

情况一:父元素的 margin-top 与第一个 in-flow 后代的 margin-top 折叠

除非父元素有:

  • border-top
  • padding-top
  • 任何 inline 内容(如文字)
  • clearance(clear 产生的)

情况二:父元素的 margin-bottom 与最后一个 in-flow 后代的 margin-bottom 折叠

除非父元素有:

  • 显式定义的 heightmin-height
  • border-bottom
  • padding-bottom

两种情况下,给父元素创建新的 block formatting context(BFC) 也能阻止 margin 折叠。

举例说明父子折叠的影响:

1
2
3
4
5
<div style="margin: 2rem; background: lavender;">
  <p style="margin: 0.4rem 0 1.2rem;">第一个段落</p>
  <p style="margin: 0.8rem 0;">第二个段落</p>
</div>
<p>我离上面的 div 有 2rem 间距</p>

第二个 <p>margin-bottom 会和父 <div>margin-bottom 折叠,结果取最大值 max(0.8rem, 2rem) = 2rem

Empty blocks(空块级元素)

如果一个块级元素的 margin-topmargin-bottom 之间没有任何 border、padding、inline 内容、heightmin-height 来分隔,那么它自己的上下 margin 会折叠在一起。

1
2
<!-- 这个空 div 上下 margin 都是 20px,但它们会折叠 -->
<div style="margin-top: 20px; margin-bottom: 20px;"></div>

结果:这个空 div 整体只占用 20px 的 margin 空间,而不是 40px。

负 margin 的情况

  • 正负混合:折叠后的 margin = 最大正值 + 最小负值(如 30px + -10px = 20px
  • 全部为负:折叠后的 margin = 最负的那个值

哪些容器不会产生折叠

display: flexdisplay: grid 的容器内部,margin 不会折叠。

阻止折叠的完整清单

以下条件任一满足即可阻止父子 margin 折叠:

阻止条件适用方向
border-top / border-bottomtop / bottom
padding-top / padding-bottomtop / bottom
内有 inline 内容(文字等)top
显式 height / min-heightbottom
创建新 BFC(如 overflow: hidden均可
clear 产生的 clearancetop

即使父元素的 margin 是 0,这些规则仍然生效:后代的 margin 有可能”穿透”父元素,跑到父元素外面。


Understanding and setting aspect ratios(理解与设置宽高比)

页面上的每个元素都有宽高,因此也就有一个 aspect ratio(宽高比)。媒体对象的”原始尺寸”——不经任何缩放、裁剪、边框影响时的大小——称为 intrinsic size(固有尺寸),由元素自身决定,不受 box sizing 或 border/margin/padding 影响。

开发中你经常希望设宽度为视口或父容器的百分比,然后让高度按比例自动变化。对 replaced elements(如图片、视频)来说,保持宽高比不只是响应式设计的需要,更直接关系到用户体验——如果不预留空间,媒体加载完成后页面会发生布局抖动(loading jank),因为之前没有给它留位子。

CSS 的 aspect-ratio 属性可以同时调控 replaced 和 non-replaced 元素的宽高比。

How the aspect-ratio property works(aspect-ratio 属性是如何工作的)

aspect-ratio 定义元素盒子的首选(preferred)宽高比。值可以是 <ratio>、关键字 auto、或两者的组合。

<ratio> 由两个正数用 / 分隔表示,或直接用一个数(等价于 <number> / 1):

1
2
3
4
5
6
7
8
9
10
/* 以下全部等价 */
aspect-ratio: 3 / 6;
aspect-ratio: 1 / 2;
aspect-ratio: 0.5 / 1;
aspect-ratio: 0.5;

/* 这些也全部等价 */
aspect-ratio: 9/6;
aspect-ratio: 3/2;
aspect-ratio: 1.5;

auto 的行为取决于元素类型:

  • 对 replaced elements 有固有宽高比的 → auto = 使用固有宽高比
  • 其他情况 → auto = 没有首选宽高比(即默认行为)

auto<ratio> 同时出现(如 aspect-ratio: auto 2/3):auto 用于有固有宽高比的 replaced elements,<ratio> 作为 fallback 首选宽高比。

⚠️ “首选”是关键aspect-ratio 只在至少一个维度的尺寸是自动(auto)时才生效。如果 width 和 height 都显式设了值,aspect-ratio 会被直接忽略。显式尺寸优先于宽高比。

对于 replaced elements,如果没有显式设尺寸(auto 不算),两个维度都会回到 intrinsic size,此时 aspect-ratio 也不会生效。

举些例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* ❌ 不生效——两维都设死了 */
.box {
  width: 300px;
  height: 200px;
  aspect-ratio: 1 / 1;  /* 被忽略,盒子还是 300×200 */
}

/* ✅ 只设了 width,高度由比例推出 = 300 × 3/4 = 225px */
.box {
  width: 300px;
  aspect-ratio: 4 / 3;
}

/* ✅ 图片设 auto 3/4——auto 走固有比例,3/4 是 fallback */
img {
  width: 300px;
  aspect-ratio: auto 3/4;
}

Adjusting aspect ratios of replaced elements(调整替换元素的宽高比)

replaced elements(如 <img><video>)自带固有宽高比。如果你只设一个维度,浏览器会自动按原比例算另一个:

1
2
3
4
img {
  width: 220px;
  /* height 自动按图片比例计算 */
}

只有同时设了 width 和 height 时,才可能破坏宽高比

1
2
3
4
5
img {
  width: 100vw;
  height: 100vh;
  /* 图片被拉伸/挤压,惨不忍睹 */
}

aspect-ratio 也能产生同样的拉伸效果,只要你只设一个维度 + 给一个不等于原图比例的值。虽然一般你不会故意这么做,但要知道存在这种可能。

Fitting replaced elements within their containers(让替换元素适配容器)

想让 replaced element 匹配容器尺寸同时保持固有宽高比,应该用 object-fit

效果
cover填满容器,裁剪超出的部分,保持比例
contain完整显示在容器内,可能留白,保持比例
fill(默认)拉伸填满,破坏比例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}

.grid-item {
  aspect-ratio: 5 / 2;  /* 容器自身的宽高比 */
}

.grid-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;  /* 填满格子,裁剪多余 */
}

Defining aspect ratios for non-replaced elements(为非替换元素设置宽高比)

non-replaced elements(如 <div><p>)没有固有宽高比,尺寸由内容决定。同一段文字在 200px 和 600px 宽的容器里行数完全不同。

replaced element 的内容来自外部,CSS 控制的是它的”框”而不是内容本身;non-replaced element 的内容由 CSS 和 HTML 直接渲染。

以前想要固定宽高比只能同时设 width 和 height 再配合 overflow: hidden。现在 aspect-ratio 提供了显式的宽高比支持,即使你不知道内容或屏幕尺寸也能锁定比例:

1
2
3
4
blockquote {
  inline-size: max-content;
  aspect-ratio: 1;  /* 正方形,height = width */
}

这里只设了 inline-size(在横排语言中就是 width),height 由 aspect-ratio: 1 自动推导出来。

对 non-replaced block-level 元素,其 inline-size 默认就是容器的 content box 宽度。所以即使不显式设尺寸,aspect-ratio 也能起作用。

基于容器尺寸创建圆形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
div, p {
  border-radius: 50%;
}

div {
  width: 200px;
  padding: 5px;
  /* 椭圆形,因为 height 由内容撑开 */
}

p {
  aspect-ratio: 1;
  /* 正圆形!width 继承 div 的 content-box 宽度(190px),height 自动 = 190px */
}

non-replaced 元素的 inline-size 默认等于容器 content box 宽,这个”自动有尺寸”的特性让 aspect-ratio 无需额外设定就能工作。

Common aspect-ratio use cases(常见用例)

让外部嵌入内容响应式

第三方嵌入(YouTube、TikTok、Instagram)通常返回 <iframe>,而 <iframe> 不像 <video> 那样能自动继承媒体文件的宽高比。

标准做法:给 iframe 容器设 aspect-ratio 匹配视频本身的比例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.youtube {
  aspect-ratio: 16/9;
  width: 100vw;
  height: auto;
}

.tiktok,
.instagram {
  aspect-ratio: 9/16;
  height: 100vh;
  width: auto;
}

/* 视口特别宽时,YouTube 改为占满高度 */
@media (aspect-ratio > 16/9) {
  .youtube {
    width: auto;
    height: 100vh;
  }
}

这样无论视口怎么变,视频比例永远正确,不会留黑边或裁掉关键内容。

但实际上iframe的内容显示是无法保证一定适配容器的。

iframe 可以理解成是HTML API,URL 传参,返回一个带独立 JS/DOM/事件循环的窗口。

不同的iframe返回可能有三种情况:

 比例黑边
大平台(YouTube)iframe在被返回时就已硬编码 16:9,平台内部做好自适应
自己平台的数据后端传 ratio 字段,动态设
完全未知只能猜比例,比如 16:9很可能有,且不可单方消除

黑边本质是跨域限制,父页面不知道内部视频比例,CSS 当然无法协助。而大平台的iframe是 SDK 级封装,自己平台的靠前后端约定,陌生 iframe 就要听天由命了。

要点速记

场景策略
replaced elements 保持比例只设一个维度,另一维度 auto
replaced elements 适配容器object-fit: cover / contain
non-replaced 元素锁定比例aspect-ratio + 一个维度尺寸
第三方嵌入(YouTube 等)给 iframe 容器设 aspect-ratio
同时设了 width 和 heightaspect-ratio 被忽略

aspect-ratio 是一个”首选”值,不是强制执行。当显式尺寸与它冲突时,显式尺寸优先。它的价值在于:你不需要知道内容能撑多高,只需要声明”我就要这个比例”。

本文由作者按照 CC BY 4.0 进行授权