HTML页面布局

HTML 页面布局

引言

网页的基本骨架

想象一下,建造一座房子。在刷上漂亮的油漆、摆放精致的家具之前,首先需要一个坚实的结构框架——地基、梁柱和墙壁。网页开发也是如此,而 HTML 就是构建这个“数字房屋”的蓝图。每一个网页的本质都是一个 HTML 文件,它由一系列被称为 标签 (tag) <> 的指令构成。一个最基础的 HTML 页面结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="zh-CN"> <!-- 页面根元素,必须有 -->
<head>
<meta charset="UTF-8">
<title>页面标题</title>
<!-- 引入 CSS、Meta、其他资源 -->
</head>
<body>
<!-- 页面显示的内容 -->
<h1>你好,世界</h1>
<p>这是我的第一个网页。</p>
</body>
</html>

让我们快速分解一下:

  • <!DOCTYPE html>: 这是一个“文档类型声明”,告诉浏览器:“接下来你读到的是一个标准的 HTML5 页面。” 这是所有 HTML 页面的第一行,必不可少。
  • <html>: 页面的根元素,像一个总容器,包裹着所有其他标签。
  • <head>: 页面的“头部”,这部分内容不会直接显示给用户。它主要负责页面配置,例如设置字符集(meta charset="UTF-8")、定义页面标题(<title>),以及引入外部 CSS 样式表和 JavaScript 脚本。
  • <body>: 页面的“身体”,这里包含了用户在浏览器窗口中看到的所有内容,比如文本、图片、视频和各种交互元素。

用“语义化”标签搭建清晰的结构

<body> 内部,我们如何组织内容呢?

最常见的工具是 <div> 标签。可以把它想象成一个通用的“盒子”,它本身没有任何特定含义,主要作用就是将相关元素圈起来,方便我们用 CSS 对它们进行统一的样式设计或布局。然而,如果整个页面都由无数个 <div> 构成,代码很快就会变得难以阅读和维护。为了解决这个问题,HTML5 引入了 语义化标签。这些标签就像给“盒子”贴上了清晰的标签,让页面结构像一篇文章一样“有章可循”,不仅方便开发者理解,对搜索引擎优化(SEO)和无障碍访问也大有裨益。

以下是一些最核心的语义化标签:

标签 作用和典型内容
<header> 页眉。通常放置网站 Logo、主标题、顶级导航栏等。
<nav> 导航区。专门用于承载主要的导航链接,如菜单栏。
<main> 主内容区。页面的核心内容,每个页面 有且仅有 一个。
<aside> 侧边栏。放置与主内容间接相关的信息,如公告、广告、相关链接等。
<article> 独立内容块。代表一篇完整的、可以独立分发的内容,如博客文章、新闻条目。
<section> 逻辑区段。将内容划分为不同的逻辑部分,通常带有一个标题(<h2>-<h6>)。
<footer> 页脚。通常包含版权信息、联系方式、备案号、次要链接等。

使用这些标签,我们可以勾勒出一个清晰的页面布局,如下图所示:

Illustration showing how semantic tags are used to implement a typical website layout.

所谓的页面布局,实际上就是排布这些标签。

浏览器如何“阅读”布局:默认文档流

我们使用了语义化标签,在没有任何 CSS 的情况下,浏览器会如何显示它们呢?

答案是:按照正常的文档流(Normal Flow)。文档流是网页布局的默认模式:HTML 元素会按照从上到下、从左到右的顺序一个一个地排列。在文档流中的元素,具有以下特征:

  • 会占据空间;
  • 会影响其它元素的位置;
  • 父容器会根据它们的实际尺寸计算自己的高度

在文档流中,元素分为两大类:

  • 块级元素 (Block-level Elements): 如 <div>, <p>, <h1>, <header>, <main> 等。它们会默认占据父容器的 一整行,自上而下垂直排列。
  • 行内元素 (Inline-level Elements): 如 <span>, <a>, <strong> 等。它们只占据自身内容的宽度,并且会在一行内从左到右水平排列,直到空间不足才会换行。

因此,下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="zh-CN"> <!-- 页面根元素,必须有 -->
<head>
<meta charset="UTF-8">
<title>页面标题</title>
<!-- 引入 CSS、Meta、其他资源 -->
</head>
<body>
<header>导航栏</header>
<div class="banner">大图</div>
<div class="content">
<div class="section">内容1</div>
<div class="section">内容2</div>
</div>
<footer>底部</footer>
</body>
</html>

在浏览器中的默认样子,其实是这样的,所有块级标签从上到下依次排列:

image-20250711150708747

我们平时看到的左右分栏、网格等复杂布局,都不是 HTML 的默认行为,而是通过 CSS 实现的。

连接 HTML 与 CSS 的桥梁:Class 属性

为了让 CSS 能够精确地控制每一个 HTML 元素,我们需要给元素一个标识。最常用的标识就是 class 属性。

class 意为“类”,我们可以为一组具有相同样式的元素定义一个类名。

第一步:在 CSS 中定义一个类

1
2
3
4
5
/* 通过一个点 . 后面跟类名来定义 */
.important-text {
color: red;
font-weight: bold;
}

第二步:在 HTML 中使用这个类

1
<p class="important-text">这是一段重要的文字。</p>

一个元素还可以同时拥有多个 class,用空格隔开,这使得样式的组合和复用变得非常灵活。HTML 和 CSS 是容错性极强的语言,浏览器即使遇到了一个没定义的 class,它也不会报错,不会阻止页面显示,只是会默默忽略这个类对应的样式而已。

1
2
<!-- 这个 div 同时应用了 box, red, bold 三个类的样式 -->
<div class="box red bold"></div>

这种做法在现代 CSS 框架中尤其普遍。

一个重要的最佳实践:始终将 结构 (HTML)表现 (CSS) 分离。虽然 HTML 自身也提供了一些用于样式的属性(如 <img width="200">),但最佳实践是将所有样式都交给 CSS 来管理。

不推荐的做法(结构与样式耦合) 推荐的做法(通过 CSS 类控制)
<div style="width: 200px; height: 100px;"></div> .box { width: 200px; height: 100px; } <br> <div class="box"></div>

这样做的好处是让代码更易于维护、复用,并且是实现响应式设计(即适配不同屏幕尺寸)的基础。

小结

到这里,我们已经基本认识了 HTML 布局的整体框架:

  1. HTML 的基本骨架由 <head><body> 标签构成。
  2. 通过使用语义化标签 (<header>, <main> 等) 可以构建清晰的页面结构。
  3. 浏览器默认的“文档流”行为是块级元素垂直排列。
  4. class 属性是连接 HTML 和 CSS 的桥梁。

CSS 布局的演化:从表格到弹性盒子

在上一节中,我们了解了 HTML 的基本结构和“文档流”的概念。我们发现,仅仅依靠 HTML 的默认行为,所有块级元素只会自上而下地堆叠起来,就像一串垂直的积木。

那么,我们如何才能打破这种单调的排列,创造出我们在网上看到的那些左右分栏、网格交错的复杂网页布局呢?

答案是 CSS (层叠样式表)。CSS 赋予了我们重塑页面结构的能力。但在我们认识当今强大而灵活的布局工具(如 Flexbox 和 Grid)之前,有必要回顾一下历史。在 CSS 布局功能还很贫乏的年代,开发者们发挥了自己的聪明才智,利用当时仅有的工具来“模拟”布局。这些方法在今天看来虽然笨拙,但它们是通往现代布局的必经之路。了解远古时期曾经使用过的布局方案,能让我们更深刻地理解现代方案引入的原因和优越性。

古老布局方案

<table> 布局:万物皆可为表格

在 CSS 远未成熟的上古时期,开发者们发现 <table> 标签是唯一能够可靠地创建网格结构的 HTML 元素。于是,一个大胆的想法诞生了:把整个网页当作一个巨大的表格来设计。基本思路就像操作 Excel 一样:将页面划分为行 (<tr>) 和单元格 (<td>),然后将导航、侧边栏、主内容等模块填充到不同的单元格里。如果发现了一些建立的很早但至今仍在运营的网站,就可能会发现这种布局实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>Table 布局示例</title>
<style>
/* 基础样式,让表格撑满屏幕 */
html, body { margin: 0; padding: 0; height: 100%; }
.layout-table {
width: 100%;
height: 100%;
border-collapse: collapse; /* 合并单元格边框 */
text-align: center;
}
.layout-table td { border: 1px solid #ccc; padding: 1em; }
.header, .footer { background-color: #f2f2f2; height: 60px; }
.sidebar { background-color: #fafafa; width: 25%; }
.main-content { background-color: #fff; }
</style>
</head>
<body>

<table class="layout-table">
<!-- 第一行: 页眉 -->
<tr class="header">
<td colspan="2"><header>页眉/导航栏</header></td>
</tr>
<!-- 第二行: 主体内容 -->
<tr>
<td class="main-content"><h1>主内容区</h1></td>
<td class="sidebar"><aside>侧边栏</aside></td>
</tr>
<!-- 第三行: 页脚 -->
<tr class="footer">
<td colspan="2"><footer>版权信息</footer></td>
</tr>
</table>

</body>
</html>

这段代码通过 colspan="2" 属性将页眉和页脚的单元格横向合并,占据两列的宽度,从而实现了典型的上中下、中间分左右的布局。

image-20250727163018708

为何被淘汰?

  • 语义混乱<table> 的语义是“展示表格化数据”,用它来布局完全违背了其初衷,导致 HTML 结构与内容含义严重脱节。
  • 结构臃肿:为了实现布局,需要嵌套大量的 <tr><td> 标签,代码可读性极差,维护困难。
  • 响应式噩梦:表格的结构是固定的、僵硬的。在手机这样的小屏幕上,很难让左右分栏的表格优雅地变为上下堆叠的结构。这使得响应式设计几乎无法实现(根据屏幕宽度改变呈现样式)。
  • 加载性能差:浏览器需要等待整个 <table> 的所有内容都加载完毕后才能开始渲染,会拖慢页面的显示速度。

【知识补充】这里涉及到了一些有关 HTML 和 CSS 的额外知识:

  • 类、ID 和标签:

    • 类(class) 是一种可以重复使用的标识符,用于给多个 HTML 元素赋予相同的样式或行为。在这段代码中,class="header"class="main-content" 等就是给这些单元格添加样式的方式;
    • ID(id) 是唯一的标识符,用于标记页面中某个特定的元素。一个 ID 在同一页面中只能使用一次,通常用于 JavaScript 操作或精确定位样式,如 #main-header
    • 标签(Tag / 元素选择器) 是直接使用 HTML 的标签名来选中元素。在这段代码中,htmlbody 就是标签选择器,它们分别选中页面的 <html><body> 元素,常用于全局样式设置,如清除默认边距、设置背景色等;

    在 CSS 中:

    • 类选择器使用点(.)表示,如 .header
    • ID 选择器使用井号(#)表示,如 #main-header
    • 标签选择器直接写标签名,如 bodytabletd,无需加前缀。
  • 后代选择器:CSS 中的后代选择器用于选中某个元素内部所有层级中符合条件的子元素。在这段代码中,.layout-table td 表示“所有 class 为 layout-table 的元素中,包含的所有 <td> 元素”(无论 <td> 是直接子元素还是嵌套在更深层的子元素中)。常用于对表格中的所有单元格统一设置样式,如边框、内边距等。

  • 并列选择器:CSS 中的并列选择器(使用逗号 , 分隔)可以同时选中多个不相关的元素,并对它们应用相同的样式。例如,html, body 选择的是页面中的 <html><body> 元素,表示对它们同时应用大括号内的样式声明。这在需要初始化多个基础元素时非常常见。

  • 复合选择器:复合选择器用于同时匹配一个元素的多个条件(例如标签名与类名同时存在)。例如,aside.left 表示选中所有 标签为 <aside> 且 class 包含 left 的元素。它强调的是“同一个元素身上同时满足多个特征”。

  • <table>, <tr>, <td>:这是 HTML 中用于构建表格结构的基本标签。其中:

    • <table> 用于定义整个表格;

    • <tr>(table row)表示表格中的一行;

    • <td>(table data)表示表格中的一个单元格(格子)。

    • colspan="2"<td> 标签的一个属性,表示该单元格要横跨两列。


float 布局:一个“美丽的意外”

随着 CSS 的发展,float(浮动)属性登场了。float 原本是 CSS 中用于图文混排的属性,允许元素向左或右“浮动”,而文字等非浮动内容会自动环绕它排布。当一个元素被设置为 float 后,它就脱离了标准文档流。它会向指定方向移动(如左边),直到碰到容器边缘或其他浮动元素,它后面的内容(非浮动元素)会围绕它流动排布。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Float 文字环绕示例</title>
<style>
.float-image {
float: left;
width: 200px;
margin: 10px 20px 10px 0; /* 上右下左的外边距,避免文字贴图太紧 */
}

p {
line-height: 1.6;
}
</style>
</head>
<body>

<img class="float-image" src="https://p1.ssl.qhimgs1.com/sdr/400__/t018ff36b414ce4d5b1.jpg" alt="示例图片">

<p>
这是一段示例文字,用于演示如何通过 CSS 中的 float 属性让文字环绕图片。
图片通过 `float: left` 向左浮动后,后面的文字就会自动在右边排布。如果文字很多,就会像现在这样环绕在图片的右侧并延伸到图片下方。
这种布局在图文新闻、博客内容中非常常见,是网页早期布局的主要手段之一。
</p>

</body>
</html>

image-20250727165913973

开发者们很快发现,既然 float 可以让一个元素“浮”起来,脱离正常的文档流,并排到左边或右边,那我们是不是可以用它来实现多列布局呢?于是,float 布局时代来临了,并统治了 Web 设计近十年。float 布局的核心思想是将需要并排的元素(如主内容区和侧边栏)都设置 float: left;float: right;,它们就会尽可能地向左或向右排列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Float 布局示例</title>
<style>
body {
margin: 0;
font-family: sans-serif;
background-color: #f0f0f0;
}

header, footer {
padding: 1em;
color: white;
text-align: center;
}

header {
background-color: #333;
}

footer {
background-color: #666;
}

.container {
width: 90%;
max-width: 960px;
margin: 20px auto;
background-color: #fff;
padding: 1em;
}

.main-content {
float: left;
width: 65%;
background-color: #fff;
padding: 1em;
}

.sidebar {
float: right;
width: 15%;
background-color: #fafafa;
padding: 1em;
}

/* 关键:清除浮动,防止父容器高度塌陷 */
.clearfix::after {
content: "";
display: table;
clear: both;
}
</style>
</head>
<body>

<header>
<h1>我的博客导航栏</h1>
</header>

<div class="container clearfix">
<div class="main-content">
<h2>主内容区</h2>
<p>
这是主内容部分,用来讲解 float 布局。你可以看到内容浮动在左侧,而侧边栏浮动在右侧。
如果没有对父容器添加 `.clearfix`,那么这个 `.container` 容器的高度将“塌陷”,导致背景无法撑开。
</p>
</div>

<div class="sidebar">
<h3>侧边栏</h3>
<p>这里是侧边栏内容,比如推荐文章、广告等。</p>
</div>
</div>

<footer>
<p>© 2025 我的博客</p>
</footer>

</body>
</html>

image-20250727171216189

前面我们提到了文档流,在文档流中的元素,具有占据空间、影响其它元素的位置、父容器会根据它们的实际尺寸来计算自己的高度的这些特点。但是,一些 CSS 属性会让元素脱离文档流,比如:

脱离方式 实现方式 效果
浮动(float) float: left/right 脱离标准流,向一侧“漂浮”,不占原来位置
绝对定位 position: absolute/fixed 脱离文档流,直接按坐标定位

这会导致一个问题,就是父元素只会被标准流中的子元素撑开,不会考虑浮动或者定位的子元素。比如我们在一个盒子里放了一些砖块(标准流元素),盒子就会自动变高来包住他们。但是如果把砖块用绳子吊起来,挂在盒子外侧,这时虽然砖块看起来在盒子里,但并没有放在盒子里占据空间,于是盒子就会以为自己是空的。也就是说,float 布局有一个致命的副作用:当一个容器内的所有子元素都浮动时,这个容器会无法识别它们的高度,导致自身高度“塌陷”为 0。这个问题,就被称之为 高度塌陷(Collapse)。比如在下面这个图里,浮动的主内容区和侧边栏,直接浮动在了 footer 的上面,而原有的 container 只有很窄的一层。

image-20250727171341477

那么如何修复塌陷问题呢?最经典的解决方案就是 clearfix hack,核心思想是给父元素添加一个看得见的东西(伪元素)来情处内部浮动的影响。典型 clearfix 的写法是:

1
2
3
4
5
.clearfix::after {
content: "";
display: table;
clear: both;
}

把父容器添加上这个类就修复了高度塌陷问题,这是怎么实现的呢?

首先,.clearfix::after {content: "";} 这一步等同于在容器最后插入了一个看不见的盒子。

接下来,display: table; 的含义是,让这个盒子变成一个块级盒子,可以参与布局。display 是 CSS 中最核心的布局属性之一,它决定了一个元素在页面上如何呈现和参与布局。常见的 display 取值如下:

说明
block 块级元素,占据一整行(如 <div>),可以设置宽高
inline 行内元素(如 <span>),不支持设置宽高,只占内容所需空间
inline-block 像行内元素那样排列,但可以设置宽高
none 不显示,元素彻底从页面消失(不是隐藏,而是完全移除)
table <table> 元素一样布局,常用于 clearfix 修复浮动塌陷
flex 启用弹性盒子布局(Flexbox)
grid 启用网格布局(Grid Layout)

clear: both; 的含义是,让这个盒子站在所有浮动元素的下面,不与它们同一行。clear 是 CSS 中用于控制浮动影响的属性。它的作用是:指定当前元素不能与哪些浮动元素并排,而是必须“避开它们”另起一行。常见取值如下:

说明
none 默认值,不清除任何浮动
left 不允许当前元素与左侧浮动元素并排(向下“躲开”左浮动元素)
right 不允许当前元素与右侧浮动元素并排
both 同时避开左浮动和右浮动元素,最常用于清除浮动
inherit 继承父元素的 clear 属性值

伪元素本身没有实际内容(content: ""),它的高度是 0。但是在设置了 clear: both 后,它会“避开”之前所有的浮动元素,强制自己出现在它们之后。同时它是一个正常流中的元素(因为它没有 float),所以父容器会把它计算进高度中;于是,父容器的高度就变成:浮动元素的最大高度 + 伪元素的 0 高度;换句话说,伪元素的位置撑起了整个容器。

虽然通过这些 hack 可以解决 float 浮动的问题,但是随着时间的推移 float 布局还是被淘汰了:

  • 本质是 Hack:float 本就不是为页面级布局设计的,使用它本身就是一种“hack”,带来了很多不符合直觉的问题,比如需要清除浮动。
  • 脆弱的对齐:float 布局对盒模型(margin, padding, border)非常敏感,稍有不慎就会导致一列“掉下去”,破坏整个布局。
  • 垂直居中困难:使用 float 实现元素的垂直居中极其困难和繁琐。
  • 代码顺序限制:在响应式设计中,我们有时希望在小屏幕上将侧边栏显示在主内容下方,但 float 布局使得改变元素的视觉顺序非常不便。

position 布局:精确的“坐标定位”

position 属性提供了另一种脱离文档流的方式,它更像是用图钉把元素钉在页面的特定位置。position 有 static, relative, absolute, fixed, sticky 几种形式,这里整理其中两种主要的定位方式:

  • position: relative相对定位

    相对于元素自己原来的位置进行偏移,不脱离文档流,主要用于微调元素位置。

    1
    <div class="box">内容</div>

    1
    2
    3
    4
    5
    .box {
    position: relative;
    top: 10px;
    left: 20px;
    }

    元素原本在正常位置上,向下移动 10px,向右移动 20px,仍然占据原来的空间。视觉上它动了,结构上它没动,所以这可能会产生的问题就是后面的元素可能和它重合。

  • position: absolute绝对定位

    相对于最近的已定位的父级元素(即设置了 position: relative/absolute/fixed 的元素)进行定位;如果找不到,就相对于 <html><body> 定位。所以它主要的用处是,比如我们做了一个数据卡片,想让数据卡片中的一段文本在卡片的某个位置显示。于是就可以把卡片设置为 position: relative,之后把卡片里的文字设置为 position: absolute

    但这样存在一个什么问题呢?就是元素的位置是完全确定的,完全脱离了文档流。假如我们用 position: absolute 设置了卡片中某一个元素的位置,卡片中的其他元素就完全感知不到这个元素了。我们也需要完全指定他们的位置,否则就很有可能发生重叠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Position 布局示例</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: sans-serif;
}

/* 顶部导航栏 */
header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 60px;
background-color: #333;
color: white;
text-align: center;
line-height: 60px;
z-index: 1000;
}

/* 页面主容器(用于包住内容) */
.main-container {
position: relative;
margin-top: 60px; /* 避开固定导航栏 */
min-height: 500px;
padding: 20px;
background-color: #f0f0f0;
}

/* 侧边栏 */
.sidebar {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 100%;
background-color: #fafafa;
padding: 1em;
border-right: 1px solid #ccc;
}

/* 主内容区 */
.main-content {
margin-left: 220px; /* 为侧边栏留出空间 */
padding: 1em;
background-color: white;
min-height: 300px;
}

/* 绝对定位的浮动块(教学用) */
.floating-box {
position: absolute;
bottom: 20px;
right: 20px;
width: 150px;
height: 100px;
background-color: #add8e6;
text-align: center;
line-height: 100px;
box-shadow: 0 0 8px rgba(0,0,0,0.2);
}

/* 页脚 */
footer {
background-color: #666;
color: white;
text-align: center;
padding: 1em;
margin-top: 20px;
}
</style>
</head>
<body>

<header>
我的导航栏(固定定位)
</header>

<div class="main-container">
<div class="sidebar">
<h3>侧边栏</h3>
<p>我是通过 absolute 定位贴在左边的。</p>
</div>

<div class="main-content">
<h1>主内容区</h1>
<p>
这是主内容区域。可以在这里尝试 position 的几种用法,包括 static、relative、absolute、fixed 等。
当前这个布局中,侧边栏是 absolute 定位,相对于最近的 relative 容器(`.main-container`)进行定位。
</p>
<div class="floating-box">
定位块
</div>
</div>
</div>

<footer>
页脚内容(在文档流中)
</footer>

</body>
</html>

采用 position 布局的基本思路就是,外层主容器用 position: relative;,作为定位参考系;内部每一个区域用 position: absolute;,自由放置在容器内的任意位置。

image-20250727180607664

但是 position 的方案也逐渐没人采用了:

  • 脱离文档流position: absolute 会让元素完全脱离文档流,这意味着它后面的元素会无视它的存在,直接占据它的位置,导致内容重叠。
  • 内容驱动性差:布局是基于写死的坐标,如果元素内的内容变多或变少,元素本身的大小不会影响到其他元素的排列,非常死板。
  • 响应式灾难:和 table 布局一样,基于坐标的定位在不同尺寸的屏幕上会完全错乱,维护成本极高。

现代 CSS 布局

在上一节中,我们回顾了 tablefloatposition 这些“老兵”。我们发现,它们要么语义不符,要么行为怪异,在构建复杂且响应式的网页时,总是让我们捉襟见肘。开发者们长久以来都在呼唤一种专为布局而生的、直观且强大的工具。终于,革命到来了。CSS 迎来了两位真正的王者:Flexbox(弹性盒子)Grid(网格)。它们彻底改变了我们编写布局的方式,将开发者从无尽的“hack”中解放出来。关于 Flexbox 和 Grid,有大量的博客文章介绍,比如阮一峰老师的网络日志,这里就不放置太多的细节了。

Flexbox (弹性盒子):一维布局

Flexbox 是一种 一维布局模型。它让我们能够轻松地控制一组项目在 单一行单一列 上的对齐、分布和排序。

要使用 Flexbox,只需要两步: 1. 在父元素(容器)上设置 display: flex;。 2. 通过容器上的一系列属性来控制子元素(项目)的布局。

一旦设置了 display: flex,Flexbox 的世界就围绕着两根轴线展开: * 主轴 (Main Axis):项目排列的主要方向。默认是水平方向(从左到右)。 * 交叉轴 (Cross Axis):与主轴垂直的轴线。默认是垂直方向(从上到下)。

img

Flexbox 最经典的一个应用场景就是制作导航栏,只需要用到 display: flex, justify-content, 和 align-items 三个属性,就可以轻松实现了一个水平分布、垂直居中的复杂导航栏布局。这在过去用 float 是难以想象的。

Grid (网格):二维布局

如果说 Flexbox 是整理一排书的工具,那么 Grid 就是整理整个图书馆书架的蓝图

Grid 是一种 二维布局模型。它允许我们同时控制行和列,将页面划分为一个网格,然后将元素精准地放置在网格的指定区域。它天生就是为构建整个网页的宏观布局而生的。使用 Grid 布局时,我们就像在画一个表格:

  • 网格轨道 (Grid Track):就是网格中的行或列。
  • 网格单元格 (Grid Cell):一行和一列交叉形成的最小单位。
  • 网格线 (Grid Line):构成网格的水平和垂直的分隔线。

相比于 table 布局,一方面,Grid 的布局由 CSS 决定,实现了结构和样式的解耦;同时,Grid 提供了非常灵活和精细的排布以及控制功能,支持响应式,还可以配合过渡、层级、动画等一起使用。

img

圣杯布局

“圣杯布局”是一种经典的左中右三栏、上有顶、下有底的页面布局,曾是 CSS 的一大难题。在有了 Grid 和 Flexbox 后,这样的布局就非常容易实现了。在进行任何布局时,基本思路就是:

  • 把 Grid 用于宏观布局 (Macro Layout):用它来搭建整个页面的骨架,比如划分出页眉、页脚、主内容区和侧边栏。
  • 把 Flexbox 用于微观布局 (Micro Layout):用它来处理页面骨架内部的组件级布局。比如,在用 Grid 划分出的页眉区域内,使用 Flexbox 来排列 Logo 和导航链接。

在下面的例子中,grid-template-areas 定义了一个 3 x 3 的网格区域:

1
2
3
4
5
6
7
┌────────┬────────┬────────┐
│ header │ header │ header │ ← 第一行
├────────┼────────┼────────┤
│ left │ main │ right │ ← 第二行
├────────┼────────┼────────┤
│ footer │ footer │ footer │ ← 第三行
└────────┴────────┴────────┘

每个引号里是一行,每个单词代表一个命名区域。在这里,"header header header" 就代表 header 这个 grid-area 占据了 3 列。后面:

1
2
grid-template-columns: 200px 1fx 200px;
grid-template-rows: auto 1fr auto;

分别定义了网格的列宽和行高。第一行定义了 3 列的宽度,左侧栏 200 px 固定宽度、中间主区自适应、右侧栏 200 px 固定宽度;第二行定义了 3 行的高度,header 高度自动、主体部分高度拉伸填满剩余空间、footer 高度自动。auto 的意思是根据内容大小自动调整尺寸,宽度和高度由里面的内容自动决定;1fr 是按比例分配剩余空间,fr 是 “fraction” 分数的意思,表示将容器中的剩余空间分成若干等份,1fr 表示 1 份。比如 grid-template-columns: 1fr 2fr;,就代表总共 3 份空间,第一列占 1/3,第二列占 2/3。min-height: 100vh; 的意思是,不论内容再怎么少,整个容器的高度至少是 min-height 的值。

在父容器中开启了 grid 并划分完了网格区域后,如何把这些网格区域划分给子元素呢?子元素用 grid-area 这个 CSS 属性把自己放进这个布局格子即可,比如 header {grid-area: header;} 就把 <header> 这个标签放到了 header 这个 grid-area 中。

1
2
3
4
5
                              "header"
"header header header" "left"
"left main right" --->> "main"
"footer footer footer" "right"
"footer"

Grid 也可以很方便的实现响应式布局,@media (max-width: 800px) {/* 这里的 CSS 只在屏幕宽度 ≤ 800px 时生效 */} 的意思是,仅当浏览器的窗口宽度 <= 800 像素时,里面的样式才会生效。下面的样式实现了在窄屏情况下,把整个页面从原始的横向三栏布局改成了单列纵向布局。而这种改变完全不需要我们修改 HTML <body> 标签中的内容,实现了内容与样式的分离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>圣杯布局(Grid + Flex)</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}

body {
font-family: sans-serif;
}

/* ✅ Grid 布局容器 */
.layout {
display: grid;
grid-template-areas:
"header header header"
"left main right"
"footer footer footer";
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}

header {
grid-area: header;
background: #333;
color: white;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}

.nav-logo {
font-weight: bold;
}

.nav-menu {
display: flex;
gap: 20px;
}

.nav-menu a {
color: white;
text-decoration: none;
}

aside.left {
grid-area: left;
background: #f3f3f3;
padding: 20px;
}

aside.right {
grid-area: right;
background: #f9f9f9;
padding: 20px;
}

main {
grid-area: main;
padding: 20px;
background: #fff;
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
}

footer {
grid-area: footer;
background: #333;
color: white;
text-align: center;
padding: 20px;
}

/* 📱 响应式支持:窄屏下转为单列 */
@media (max-width: 800px) {
.layout {
grid-template-areas:
"header"
"left"
"main"
"right"
"footer";
grid-template-columns: 1fr;
}

.nav-menu {
flex-direction: column;
gap: 10px;
}
}
</style>
</head>
<body>

<div class="layout">

<!-- Header 导航 -->
<header>
<div class="nav-logo">MySite</div>
<nav class="nav-menu">
<a href="#">首页</a>
<a href="#">产品</a>
<a href="#">联系我们</a>
</nav>
</header>

<!-- 左侧栏 -->
<aside class="left">左侧菜单或导航</aside>

<!-- 主内容 -->
<main>这是主要内容区域</main>

<!-- 右侧栏 -->
<aside class="right">右侧推荐或广告</aside>

<!-- Footer -->
<footer>底部信息 &copy; 2025</footer>

</div>

</body>
</html>

image-20250728110932017

CSS 框架

在前几节中,我们从 HTML 的基本结构一路走来,掌握了 Flexbox 和 Grid 这两大现代布局利器。理论上,我们现在已经拥有了从零开始构建任何复杂布局的能力。但在现实世界的项目开发中,效率是关键。如果我们为每一个按钮、每一个卡片都手动编写样式,不仅耗时耗力,还难以保证整个项目风格的统一性。为了解决这个问题,社区为我们提供了 CSS 框架。它们是预先编写好的 CSS 和(有时是)JavaScript 代码库,旨在让我们能够快速、一致地构建美观的 Web 界面。

CSS 框架 vs. 前端框架

在深入探讨之前,我们必须先澄清一个常常让初学者困惑的概念。

  • CSS 框架 (如 Bootstrap, Tailwind CSS, Bulma)
    • 核心职责: 关注 “外观和感觉” (Look and Feel)
    • 它们是什么: 本质上是一套精心设计的 CSS 样式集合。它们提供了一系列预设的样式类和工具,用于快速实现布局、排版、颜色、间距等视觉效果。可以将它们看作是一套高质量的、现成的“网页皮肤和装修材料”。
    • 工作方式: 我们将框架提供的 CSS 文件引入到自己的项目中,然后在 HTML 标签上添加指定的 class 属性来应用样式。
  • 前端框架 (如 React, Vue, Angular)
    • 核心职责: 关注 “数据和逻辑” (Data and Logic)
    • 它们是什么: 是构建复杂单页应用(SPA)的完整解决方案。它们提供了一整套管理数据状态、组件化开发、路由、与服务器交互等功能的工具和范式。它们是构建应用程序的“建筑结构、水电系统和智能家居核心”。
    • 工作方式: 通常需要使用 JavaScript (或 TypeScript) 来定义组件、管理状态,并由框架负责将这些动态数据渲染为最终的 HTML。

简单来说,CSS 框架解决“这个按钮长什么样”,而前端框架解决“点击这个按钮后会发生什么”。它们处理的是不同层面的问题,并且常常可以协同工作(例如,在一个 React 项目中使用 Tailwind CSS)。

如何使用 CSS 框架

使用像 Bootstrap 或者 Tailwind 这些 CSS 框架非常简单,不像前端框架一样需要 nodejs 等环境,只需要引入框架的 .css 文件或者 .js 文件即可,然后在 HTML 中使用他们提供的类名。

比如对于 Bootstrap:

1
2
3
4
5
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">

<!-- Bootstrap 5 JS(包含组件交互所需的 JS,如折叠、弹窗等)-->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>

对于 Tailwind:

1
2
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>

其中,.js.min.js 文件的功能都是完全一样的,只不过 .min.js 把所有空格换行都压缩掉了,变量名也进行了简化,使得体积变小、加载速度变快。

两大 CSS 框架流派:组件式与原子化

现代 CSS 框架大致可以分为两种哲学流派:一种是像 Bootstrap 这样的“组件式”框架,另一种是以 Tailwind CSS 为代表的“原子化”框架。它们解决的是同一个问题——如何快速、高效、可维护地构建网页界面——但出发点却完全不同。

Bootstrap 是最早普及的前端框架之一,它提供了大量预设的 UI 组件,比如按钮、卡片、导航栏、表格等。使用它就像在 UI 商店中挑选模块,直接组合使用即可。这些组件通常通过语义化的类名(如 .btn.card.navbar)来调用,不需要手动编写太多 CSS 代码。Bootstrap 背后封装的是大量原生的 CSS 功能,尤其是它的栅格系统,其实就是基于 flexbox 实现的响应式布局机制。开发者只需通过 .row.col-6 等类名,就可以完成复杂的分栏排布,无需直接写 display: flexjustify-content 等原生样式。

相较而言,Tailwind CSS 走的是完全不同的路线。它不给你组件,而是给你一整套最基础的 CSS 工具类——每个类名都只做一件事,比如 p-4 代表 padding: 1remtext-center 表示文本居中,grid 就是启用 display: grid 布局。这种“原子化”的方式把样式定义拆解成最小单位,开发者通过组合这些类名就能构建出任何所需的界面。虽然看起来 HTML 代码中会充满各种 class,但正是这些细粒度的类,背后封装了几乎所有 CSS 的核心能力,包括 Grid 和 Flexbox。你不再需要写 CSS 文件,直接在标签上用类名就能控制布局、间距、颜色、层级等几乎所有表现。

两者的差异不仅体现在编码方式上,更体现在思维方式上。使用 Bootstrap 更像是调用“成品家具”,适合快速成型和管理后台类项目;而使用 Tailwind 更像是在自由拼装积木,适合设计师驱动、追求高度自定义的前端项目。前者更注重结构和约定,后者更强调灵活和控制权。无论选择哪种框架,实质上都不再需要从零写出 display: flexgrid-template-columnspadding: 20px 这类原生 CSS。因为这些框架已经对它们进行了高度封装与语义化。

两种布局思路

Bootstrap 的布局基于 Flexbox 栅格系统,它预设了 12 栏(12 列)结构,关键词是 container + row + col-*。基本结构是:

1
2
3
4
5
6
<div class="container">
<div class="row">
<div class="col-6">左边</div>
<div class="col-6">右边</div>
</div>
</div>
  • .container:中心内容区域(有左右 margin 和 max-width)
  • .row:表示一行,自动处理子元素的左右间距
  • .col-*:表示这一列占几个单位(共 12 份)

Bootstrap 通过断点来实现响应式:

类前缀 含义 像素值
col-* 所有屏幕尺寸(默认) <576px
col-sm-* 小屏以上 ≥576px
col-md-* 中屏以上 ≥768px
col-lg-* 大屏以上 ≥992px
col-xl-* 超大屏以上 ≥1200px
col-xxl-* 特超大屏 ≥1400px

可以针对不同屏幕定义不同的宽度,但最好要保证在不同宽度的组合下,元素列宽之和还是 12,否则可能会出现不正常的显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>圣杯布局 - Bootstrap</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

<!-- Header -->
<header class="bg-dark text-white p-3 mb-3">
<div class="container d-flex justify-content-between">
<div>Logo</div>
<nav>
<a href="#" class="text-white me-3">首页</a>
<a href="#" class="text-white">关于</a>
</nav>
</div>
</header>

<!-- Main Content -->
<div class="container">
<div class="row">
<!-- 左侧栏 -->
<aside class="col-12 col-md-3 mb-3">
<div class="bg-light p-3">左侧栏</div>
</aside>

<!-- 主内容 -->
<main class="col-12 col-md-6 mb-3">
<div class="bg-white border p-3">主内容区域</div>
</main>

<!-- 右侧栏 -->
<aside class="col-12 col-md-3 mb-3">
<div class="bg-light p-3">右侧栏</div>
</aside>
</div>
</div>

<!-- Footer -->
<footer class="bg-dark text-white text-center py-3 mt-3">
页面底部 &copy; 2025
</footer>

</body>
</html>

image-20250728115532114

Tailwind 直接使用原生 CSS Grid 或 Flexbox 的语义封装类,关键词是 grid + grid-cols-* + col-span-*

1
2
3
4
5
<div class="grid grid-cols-12 gap-4">
<div class="col-span-3">左侧栏</div>
<div class="col-span-6">主内容</div>
<div class="col-span-3">右侧栏</div>
</div>

grid 启用 CSS Grid 布局,grid-cols-12 把整行分成 12 列,col-span-X 当前元素跨几列,gap-4 网格之间留多少间距。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>圣杯布局 - Tailwind</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 text-gray-800">

<!-- Header -->
<header class="bg-gray-800 text-white p-4 flex justify-between items-center">
<div>Logo</div>
<nav class="flex gap-4">
<a href="#" class="hover:underline">首页</a>
<a href="#" class="hover:underline">关于</a>
</nav>
</header>

<!-- 主体布局 -->
<div class="grid grid-cols-1 md:grid-cols-12 gap-4 p-4">
<!-- 左侧栏 -->
<aside class="md:col-span-3 bg-white p-4 shadow">左侧栏</aside>

<!-- 主内容 -->
<main class="md:col-span-6 bg-white p-4 shadow">主内容区域</main>

<!-- 右侧栏 -->
<aside class="md:col-span-3 bg-white p-4 shadow">右侧栏</aside>
</div>

<!-- Footer -->
<footer class="bg-gray-800 text-white text-center py-4">
页面底部 &copy; 2025
</footer>

</body>
</html>

image-20250728115558221