CSS 布局
CSS 布局是网页设计的核心,决定了元素在页面上的位置和排列方式。本章将系统介绍 CSS 布局的核心概念和技术,从基础的定位到现代的 Flexbox 和 Grid 布局。
布局基础概念
在深入具体技术之前,我们需要理解几个核心概念,这些概念是理解 CSS 布局行为的关键。
正常文档流
正常文档流(Normal Flow)是页面元素的默认布局方式。在正常流中,块级元素从上到下依次排列,行内元素从左到右依次排列。
<div>块级元素1</div>
<div>块级元素2</div>
<span>行内元素1</span>
<span>行内元素2</span>
脱离正常文档流的方式有:
- 浮动(
float) - 绝对定位(
position: absolute或fixed)
包含块
包含块(Containing Block)是元素定位和尺寸计算的参考框。理解包含块对于掌握定位布局至关重要。
元素的包含块通常是其最近的块级祖先元素的内容区域,但有特殊情况:
| 定位方式 | 包含块 |
|---|---|
static / relative | 最近的块级祖先元素的内容区域 |
absolute | 最近的定位祖先元素(非 static)的内边距区域 |
fixed | 视口(viewport) |
absolute / fixed 且祖先有 transform | 该祖先元素的内边距区域 |
/* 父元素成为子元素的包含块 */
.container {
position: relative; /* 使其成为定位子元素的包含块 */
}
.child {
position: absolute;
top: 10px; /* 相对于 .container 定位 */
left: 10px;
}
定位 Position
position 属性控制元素的定位方式,是 CSS 布局的基础工具。
position 的五种取值
static(静态定位,默认值)
元素按照正常文档流布局,top、right、bottom、left 和 z-index 属性无效。
.element {
position: static; /* 默认值 */
}
这是大多数元素的默认行为,通常不需要显式设置。
relative(相对定位)
元素保持在正常文档流中,但可以相对于其原始位置进行偏移。偏移不影响其他元素的位置——元素原本占据的空间仍然保留。
.relative-box {
position: relative;
top: 20px; /* 向下移动 20px */
left: 30px; /* 向右移动 30px */
}
重要特性:
- 元素原本占据的空间保留
- 可以使用
z-index控制层级 - 常作为绝对定位子元素的包含块
常见用途:
- 微调元素位置
- 为绝对定位子元素提供定位上下文
- 创建层叠效果
<div class="container">
<div class="box">正常位置</div>
<div class="box relative">偏移位置</div>
</div>
<style>
.container {
border: 2px solid #333;
padding: 20px;
}
.box {
width: 100px;
height: 100px;
background: #3498db;
margin: 10px;
}
.relative {
position: relative;
top: 20px;
left: 20px;
background: #e74c3c;
/* 原位置仍保留,其他元素不会填补 */
}
</style>
absolute(绝对定位)
元素从正常文档流中移除,不再占据空间。它相对于最近的定位祖先元素(position 非 static)定位,如果没有则相对于初始包含块(通常是视口)。
.absolute-box {
position: absolute;
top: 50px;
right: 20px;
}
重要特性:
- 脱离文档流,不占据空间
- 自动创建新的 BFC
- 默认宽度和高度由内容决定
- 可以通过设置
top和bottom(或left和right)同时拉伸元素
常见用途:
- 弹窗、下拉菜单
- 图标叠加在图片上
- 工具提示
<div class="card">
<img src="photo.jpg" alt="照片">
<span class="badge">新</span>
</div>
<style>
.card {
position: relative; /* 成为绝对定位子元素的参考 */
display: inline-block;
}
.card img {
display: block;
max-width: 300px;
}
.badge {
position: absolute;
top: 10px;
right: 10px;
background: #e74c3c;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
</style>
拉伸技巧: 同时设置两个方向的属性可以拉伸元素填充可用空间:
.overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
/* 完全覆盖包含块 */
}
fixed(固定定位)
元素从正常文档流中移除,相对于视口定位。即使页面滚动,元素位置也不会改变。
.fixed-header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 60px;
background: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
重要特性:
- 始终相对于视口定位(除非祖先有
transform等属性) - 不随页面滚动
- 始终创建新的层叠上下文
常见用途:
- 固定导航栏
- 返回顶部按钮
- 悬浮广告
<!-- 固定底部操作栏 -->
<div class="fixed-bottom-bar">
<button>取消</button>
<button>确认</button>
</div>
<style>
.fixed-bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
padding: 15px 20px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: flex-end;
gap: 10px;
}
/* 防止内容被固定栏遮挡 */
body {
padding-bottom: 80px;
}
</style>
sticky(粘性定位)
粘性定位是相对定位和固定定位的混合体。元素在正常文档流中,当滚动到指定阈值时,会"粘"在该位置。
.sticky-header {
position: sticky;
top: 0; /* 当滚动到距离顶部 0px 时固定 */
background: white;
}
工作原理:
- 元素首先按正常文档流定位
- 当滚动使其到达指定阈值(如
top: 0),元素变为固定定位 - 元素会"粘"在其包含块内,当包含块滚出视口时,元素也会随之滚出
重要特性:
- 需要指定至少一个定位属性(
top、right、bottom、left) - 始终创建新的层叠上下文
- 需要理解包含块的概念——元素只能在其包含块范围内"粘"
<div class="container">
<h2 class="sticky">A</h2>
<p>Apple</p>
<p>Apricot</p>
<p>Avocado</p>
<h2 class="sticky">B</h2>
<p>Banana</p>
<p>Blueberry</p>
<h2 class="sticky">C</h2>
<p>Cherry</p>
<p>Coconut</p>
</div>
<style>
.container {
height: 300px;
overflow-y: auto;
}
.sticky {
position: sticky;
top: 0;
background: #3498db;
color: white;
padding: 10px;
margin: 0;
}
</style>
注意事项:
- 父元素不能设置
overflow: hidden、overflow: auto或overflow: scroll(除非这是预期的滚动容器) - 需要指定阈值属性才能生效
z-index 与层叠顺序
当元素发生重叠时,z-index 属性控制它们的堆叠顺序。
.behind {
position: absolute;
z-index: 1;
}
.in-front {
position: absolute;
z-index: 2; /* 会显示在 .behind 上方 */
}
z-index 仅对定位元素(position 非 static)有效。数值越大,元素越靠前。
块格式化上下文 BFC
BFC(Block Formatting Context,块格式化上下文)是 CSS 布局中一个非常重要但常被忽视的概念。理解 BFC 能帮助你解决很多看似"诡异"的布局问题。
什么是 BFC?
BFC 是 Web 页面可视渲染的一部分,是一个独立的渲染区域。在这个区域内,块级盒子的布局和浮动元素与其他元素的交互都遵循特定的规则。
可以把 BFC 理解为一个"封闭的盒子",盒子内部的布局不会影响外部,外部的布局也不会影响内部。
什么情况会创建 BFC?
以下情况会创建新的 BFC:
| 触发条件 | 说明 |
|---|---|
根元素 <html> | 页面的根元素始终是 BFC |
float 非 none | 浮动元素 |
position 为 absolute 或 fixed | 绝对定位元素 |
display 为 inline-block | 行内块元素 |
display 为 flow-root | 专门用于创建 BFC(推荐) |
overflow 非 visible | 如 overflow: auto、hidden |
display 为 flex / inline-flex 的子项 | Flex 项目的子元素 |
display 为 grid / inline-grid 的子项 | Grid 项目的子元素 |
contain 为 layout、content 或 paint | CSS Containment |
BFC 的三个核心特性
1. 包含内部浮动
BFC 会包含其内部的浮动元素,防止浮动元素溢出容器。
问题场景: 当容器内只有浮动元素时,容器高度会塌陷为 0。
<div class="container">
<div class="float-item">浮动元素</div>
<div class="float-item">浮动元素</div>
</div>
<style>
.container {
border: 2px solid #333;
/* 此时容器高度为 0,边框会塌陷 */
}
.float-item {
float: left;
width: 100px;
height: 100px;
background: #3498db;
}
</style>
解决方案: 让容器创建 BFC。
/* 方案1:使用 overflow(可能有副作用) */
.container {
overflow: auto;
}
/* 方案2:使用 display: flow-root(推荐,无副作用) */
.container {
display: flow-root;
}
2. 排除外部浮动
BFC 不会与浮动元素重叠,而是会避开浮动元素。
问题场景: 正常流中的元素会与浮动元素重叠。
<div class="float-left">左浮动</div>
<div class="content">内容区域</div>
<style>
.float-left {
float: left;
width: 100px;
height: 100px;
background: #3498db;
}
.content {
/* 内容会环绕浮动元素 */
background: #e74c3c;
}
</style>
解决方案: 让内容区域创建 BFC,它会自动避开浮动元素。
.content {
display: flow-root; /* 或 overflow: auto */
background: #e74c3c;
/* 现在内容区域会在浮动元素右侧显示 */
}
这个特性常用于实现两列布局:
<div class="sidebar">侧边栏</div>
<div class="main-content">主内容</div>
<style>
.sidebar {
float: left;
width: 200px;
height: 300px;
background: #3498db;
}
.main-content {
display: flow-root;
height: 300px;
background: #e74c3c;
/* 主内容自动占据剩余空间,不需要设置 margin-left */
}
</style>
3. 阻止外边距合并
相邻块级元素之间的垂直外边距会发生合并,但如果其中一个元素创建了 BFC,则可以阻止这种合并。
问题场景:
<div class="box1">Box 1</div>
<div class="wrapper">
<div class="box2">Box 2</div>
</div>
<style>
.box1, .box2 {
margin: 20px 0;
background: #3498db;
}
/* box1 和 box2 的外边距会合并,总间距是 20px 而不是 40px */
</style>
解决方案:
.wrapper {
display: flow-root; /* 创建 BFC 阻止外边距合并 */
}
/* 现在总间距是 40px */
BFC 实践技巧
清除浮动的最佳实践:
/* 传统方法(不推荐) */
.clearfix::after {
content: "";
display: table;
clear: both;
}
/* 现代 BFC 方法(推荐) */
.clearfix {
display: flow-root;
}
创建自适应两栏布局:
<div class="layout">
<aside class="sidebar">侧边栏</aside>
<main class="content">主内容</main>
</div>
<style>
.layout {
display: flow-root; /* 确保包含浮动 */
}
.sidebar {
float: left;
width: 250px;
background: #f5f5f5;
}
.content {
display: flow-root; /* 避开浮动,自动填充剩余空间 */
background: white;
}
层叠上下文
层叠上下文(Stacking Context)是理解元素层叠顺序的关键概念。每个层叠上下文都是独立的,内部元素的 z-index 只在这个上下文内部有效。
什么是层叠上下文?
层叠上下文可以想象成一个"层叠组",组内的元素按照特定规则排序,整个组作为一个整体参与更高层级的排序。
创建层叠上下文的条件
以下情况会创建新的层叠上下文:
| 条件 | 说明 |
|---|---|
根元素 <html> | 页面的根层叠上下文 |
position 非 static 且 z-index 非 auto | 定位元素 |
position 为 fixed 或 sticky | 固定/粘性定位始终创建 |
opacity 小于 1 | 透明度变化 |
transform 非 none | 变换属性 |
filter 非 none | 滤镜效果 |
will-change 指定相关属性 | 性能优化提示 |
isolation: isolate | 显式创建隔离 |
Flex/Grid 子项且 z-index 非 auto | 弹性/网格项目 |
层叠上下文的层级规则
核心规则: 子元素的 z-index 只在父级层叠上下文内部有效。
<div class="parent-a">
<div class="child-a">z-index: 9999</div>
</div>
<div class="parent-b">
<div class="child-b">z-index: 1</div>
</div>
<style>
.parent-a {
position: relative;
z-index: 1; /* 父级 z-index 较低 */
}
.parent-b {
position: relative;
z-index: 2; /* 父级 z-index 较高 */
}
.child-a {
position: absolute;
z-index: 9999; /* 虽然数值很大,但只在 parent-a 内部有效 */
background: red;
}
.child-b {
position: absolute;
z-index: 1;
background: blue;
}
/* 结果:child-b 会覆盖 child-a,因为 parent-b 的 z-index 更高 */
</style>
层叠顺序
在同一层叠上下文中,元素的层叠顺序从低到高为:
- 层叠上下文的背景和边框
- 负
z-index的子元素 - 块级元素(正常文档流)
- 浮动元素
- 行内元素
z-index: 0或auto的子元素- 正
z-index的子元素
<div class="container">
<div class="negative-z">z-index: -1</div>
<div class="block">块级元素</div>
<div class="float">浮动元素</div>
<span class="inline">行内元素</span>
<div class="positive-z">z-index: 1</div>
</div>
<style>
.container {
position: relative;
background: #f5f5f5;
padding: 20px;
}
.negative-z {
position: absolute;
z-index: -1; /* 会被容器的背景遮挡 */
background: red;
}
.float {
float: left;
background: green;
}
.positive-z {
position: relative;
z-index: 1; /* 最上层 */
background: blue;
}
</style>
Flexbox 弹性布局
Flexbox 是一种一维布局模型,非常适合处理单行或单列的元素排列。它能轻松实现居中、等分空间、自适应宽度等常见布局需求。
Flexbox 的核心概念
Flexbox 布局由两个部分组成:
- Flex 容器(Container):设置
display: flex的父元素 - Flex 项目(Item):容器的直接子元素
┌─────────────────────────────────────────────┐
│ Flex Container │
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Item 1 │ │ Item 2 │ │ Item 3 │ │
│ └────────┘ └────────┘ └────────┘ │
│ │
│ ←──────────── 主轴 (main axis) ──────────→ │
│ ↓ │
│ 交叉轴 (cross axis) │
│ ↓ │
└─────────────────────────────────────────────┘
启用 Flexbox
.container {
display: flex; /* 块级弹性容器 */
display: inline-flex; /* 行内弹性容器 */
}
容器属性
flex-direction 主轴方向
.container {
flex-direction: row; /* 左到右(默认) */
flex-direction: row-reverse; /* 右到左 */
flex-direction: column; /* 上到下 */
flex-direction: column-reverse; /* 下到上 */
}
flex-wrap 换行
.container {
flex-wrap: nowrap; /* 不换行(默认) */
flex-wrap: wrap; /* 换行 */
flex-wrap: wrap-reverse; /* 反向换行 */
}
flex-flow 简写
.container {
flex-flow: row wrap; /* direction + wrap */
}
justify-content 主轴对齐
.container {
justify-content: flex-start; /* 起点(默认) */
justify-content: flex-end; /* 终点 */
justify-content: center; /* 居中 */
justify-content: space-between; /* 两端对齐,中间等分 */
justify-content: space-around; /* 每个项目两侧等分 */
justify-content: space-evenly; /* 完全等分(包括边缘) */
}
align-items 交叉轴对齐
.container {
align-items: stretch; /* 拉伸填满(默认) */
align-items: flex-start; /* 起点 */
align-items: flex-end; /* 终点 */
align-items: center; /* 居中 */
align-items: baseline; /* 基线对齐 */
}
gap 间距
.container {
gap: 20px; /* 行和列间距相同 */
gap: 20px 10px; /* 行间距 列间距 */
row-gap: 20px; /* 行间距 */
column-gap: 10px; /* 列间距 */
}
项目属性
flex-grow 放大比例
当容器有剩余空间时,项目按 flex-grow 的比例分配剩余空间。
.item {
flex-grow: 0; /* 默认,不放大 */
}
.item-1 { flex-grow: 1; }
.item-2 { flex-grow: 2; } /* 这个项目分到的空间是上一个的 2 倍 */
flex-shrink 缩小比例
当容器空间不足时,项目按 flex-shrink 的比例缩小。
.item {
flex-shrink: 1; /* 默认,允许缩小 */
}
.no-shrink {
flex-shrink: 0; /* 不缩小 */
}
flex-basis 初始大小
项目在分配剩余空间之前的初始大小。
.item {
flex-basis: auto; /* 默认,使用项目自身宽度 */
flex-basis: 200px; /* 固定初始宽度 */
flex-basis: 25%; /* 百分比 */
}
flex 简写
.item {
flex: 0 1 auto; /* 默认值:grow shrink basis */
/* 常用简写 */
flex: 1; /* flex: 1 1 0% — 可放大可缩小,初始为0 */
flex: auto; /* flex: 1 1 auto — 可放大可缩小,初始为内容宽度 */
flex: none; /* flex: 0 0 auto — 不放大不缩小,固定大小 */
}
align-self 单独对齐
单个项目可以覆盖容器的 align-items 设置。
.item {
align-self: auto; /* 继承容器的 align-items */
align-self: flex-start;
align-self: center;
align-self: stretch;
}
order 排列顺序
.item:first-child { order: 2; }
.item:nth-child(2) { order: 1; }
/* 项目会按 order 值从小到大排列 */
Flexbox 实战案例
水平垂直居中
.center-container {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100vh;
}
导航栏
<nav class="navbar">
<div class="logo">Logo</div>
<ul class="nav-links">
<li><a href="#">首页</a></li>
<li><a href="#">产品</a></li>
<li><a href="#">关于</a></li>
</ul>
<button class="login-btn">登录</button>
</nav>
<style>
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
height: 60px;
background: #2c3e50;
color: white;
}
.nav-links {
display: flex;
gap: 30px;
list-style: none;
margin: 0;
padding: 0;
}
.nav-links a {
color: white;
text-decoration: none;
}
.login-btn {
padding: 8px 20px;
background: #3498db;
border: none;
color: white;
border-radius: 4px;
cursor: pointer;
}
</style>
卡片网格
<div class="card-grid">
<div class="card">Card 1</div>
<div class="card">Card 2</div>
<div class="card">Card 3</div>
<div class="card">Card 4</div>
</div>
<style>
.card-grid {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.card {
flex: 1 1 250px; /* 最小宽度 250px,自动填充 */
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
</style>
圣杯布局
<div class="layout">
<header class="header">Header</header>
<div class="content-wrapper">
<aside class="sidebar">Sidebar</aside>
<main class="main">Main Content</main>
</div>
<footer class="footer">Footer</footer>
</div>
<style>
.layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.content-wrapper {
display: flex;
flex: 1;
}
.sidebar {
width: 200px;
background: #f5f5f5;
}
.main {
flex: 1;
padding: 20px;
}
.header, .footer {
padding: 20px;
background: #2c3e50;
color: white;
}
</style>
Grid 网格布局
Grid 是一种二维布局系统,可以同时控制行和列,非常适合复杂的页面布局。
Grid 的核心概念
┌────────────────────────────────────────────────┐
│ Grid Container │
│ ┌──────────┬──────────┬──────────┐ │
│ │ Grid Line│ │ │ │
│ │ ↓ │ │ │ │
│ ├──────────┼──────────┼──────────┤ ← Grid Line│
│ │ │ Grid │ │ │
│ │ │ Cell │ │ │
│ ├──────────┼──────────┼──────────┤ │
│ │ │ │ │ │
│ └──────────┴──────────┴──────────┘ │
│ │
│ ←── Grid Track (行/列轨道) ──→ │
└────────────────────────────────────────────────┘
启用 Grid
.container {
display: grid;
display: inline-grid;
}
容器属性
grid-template-columns 定义列
.container {
/* 固定宽度 */
grid-template-columns: 200px 200px 200px;
/* 百分比 */
grid-template-columns: 25% 50% 25%;
/* fr 单位(比例份数) */
grid-template-columns: 1fr 2fr 1fr; /* 中间列是两侧的 2 倍宽 */
/* repeat 函数 */
grid-template-columns: repeat(3, 1fr); /* 三等分 */
/* 自动填充 */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
/* 自动适应(更紧凑) */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
grid-template-rows 定义行
.container {
grid-template-rows: 100px auto 100px; /* 头尾固定,中间自适应 */
}
gap 间距
.container {
gap: 20px; /* 行和列间距 */
row-gap: 20px; /* 行间距 */
column-gap: 30px; /* 列间距 */
}
grid-template-areas 区域命名
使用命名区域直观地定义布局结构。
.container {
grid-template-areas:
"header header header"
"sidebar main main"
"footer footer footer";
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
justify-items / align-items 项目对齐
.container {
justify-items: start | end | center | stretch; /* 水平 */
align-items: start | end | center | stretch; /* 垂直 */
place-items: center; /* 简写:同时设置 */
}
justify-content / align-content 网格对齐
当网格小于容器时,控制整个网格的对齐方式。
.container {
justify-content: start | end | center | space-between | space-around;
align-content: start | end | center | space-between | space-around;
}
项目属性
grid-column / grid-row 跨越
.item {
/* 起始线 / 结束线 */
grid-column: 1 / 3; /* 跨越第 1 到第 3 条列线 */
grid-row: 1 / 2;
/* 起始线 / span 跨越数 */
grid-column: 1 / span 2; /* 从第 1 条线开始跨越 2 列 */
}
grid-area 区域定位
.item {
grid-area: header; /* 使用命名区域 */
/* 或直接指定:row-start / col-start / row-end / col-end */
grid-area: 1 / 1 / 3 / 4;
}
Grid 实战案例
响应式图片画廊
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 15px;
}
.gallery img {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 8px;
}
经典页面布局
<div class="page">
<header class="page-header">Header</header>
<aside class="page-sidebar">Sidebar</aside>
<main class="page-main">Main Content</main>
<footer class="page-footer">Footer</footer>
</div>
<style>
.page {
display: grid;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: 250px 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
.page-header { grid-area: header; }
.page-sidebar { grid-area: sidebar; }
.page-main { grid-area: main; }
.page-footer { grid-area: footer; }
/* 移动端响应式 */
@media (max-width: 768px) {
.page {
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
grid-template-columns: 1fr;
}
}
</style>
十二列网格系统
.grid-12 {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 20px;
}
.col-1 { grid-column: span 1; }
.col-2 { grid-column: span 2; }
.col-3 { grid-column: span 3; }
.col-4 { grid-column: span 4; }
.col-6 { grid-column: span 6; }
.col-12 { grid-column: span 12; }
Subgrid 子网格
当网格项目本身也是一个网格容器时,默认情况下它会创建独立的网格轨道,与父网格没有关联。Subgrid(子网格)允许嵌套网格继承父网格的轨道定义,实现嵌套元素与父网格的完美对齐。
为什么需要子网格?
考虑这样一个场景:你创建了一个卡片网格布局,每张卡片内有标题、内容和底部信息,希望这些内部元素在不同卡片之间能够对齐。
<div class="grid">
<div class="card">
<h3>标题可能很长需要换行</h3>
<p>内容区域</p>
<footer>底部信息</footer>
</div>
<div class="card">
<h3>短标题</h3>
<p>这是一段很长的内容,可能需要多行显示...</p>
<footer>底部</footer>
</div>
</div>
传统方式无法让不同卡片的标题、内容、底部各自对齐。子网格完美解决了这个问题。
基本语法
/* 父网格 */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto auto auto; /* 三行:标题、内容、底部 */
gap: 20px;
}
/* 子网格 */
.card {
display: grid;
grid-template-rows: subgrid; /* 继承父网格的行轨道 */
grid-row: span 3; /* 跨越三行 */
}
列子网格
当子网格需要继承父网格的列轨道时:
<div class="parent-grid">
<div class="item">
<div class="subitem">A</div>
<div class="subitem">B</div>
<div class="subitem">C</div>
</div>
</div>
<style>
.parent-grid {
display: grid;
grid-template-columns: repeat(9, 1fr);
grid-template-rows: repeat(4, minmax(100px, auto));
}
.item {
display: grid;
grid-column: 2 / 7; /* 跨越 5 列 */
grid-row: 2 / 4;
grid-template-columns: subgrid; /* 继承父网格的列定义 */
grid-template-rows: repeat(3, 80px);
}
.subitem {
grid-column: 3 / 6; /* 在子网格内的列位置 */
grid-row: 1 / 3;
}
</style>
子网格跨越父网格的 5 列,因此子网格内部也有 5 列轨道,大小与父网格对应轨道完全相同。
行子网格
当子网格需要继承父网格的行轨道时:
.parent-grid {
display: grid;
grid-template-columns: repeat(9, 1fr);
grid-template-rows: repeat(4, minmax(100px, auto));
}
.item {
display: grid;
grid-column: 2 / 7;
grid-row: 2 / 4;
grid-template-columns: repeat(3, 1fr); /* 独立定义列 */
grid-template-rows: subgrid; /* 继承父网格的行定义 */
}
双向子网格
同时在行和列两个维度使用子网格:
.item {
display: grid;
grid-column: 2 / 7;
grid-row: 2 / 4;
grid-template-columns: subgrid; /* 继承列 */
grid-template-rows: subgrid; /* 继承行 */
}
间距继承
子网格会继承父网格的 gap 值,但可以覆盖:
.parent-grid {
display: grid;
grid-template-columns: repeat(9, 1fr);
gap: 20px;
}
.item {
display: grid;
grid-template-columns: subgrid;
grid-template-rows: subgrid;
row-gap: 0; /* 覆盖行的间距 */
}
命名网格线
父网格的命名线会传递给子网格,子网格也可以定义自己的命名线:
.parent-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr [col-start] 1fr 1fr 1fr [col-end] 1fr 1fr 1fr;
}
.item {
display: grid;
grid-template-columns: subgrid [sub-a] [sub-b] [sub-c] [sub-d] [sub-e] [sub-f];
/* 子网格同时拥有父网格和自己的命名线 */
}
.subitem {
grid-column: col-start / col-end; /* 使用父网格的命名线 */
}
.subitem2 {
grid-column: sub-b / sub-d; /* 使用子网格的命名线 */
}
实战案例:对齐卡片内容
<div class="cards">
<article class="card">
<h3>简短标题</h3>
<p>这是一段内容文字,可能比较短。</p>
<footer>2024-01-15</footer>
</article>
<article class="card">
<h3>这是一个非常非常长的标题可能会换行显示</h3>
<p>这是一段较长的内容文字,需要占据更多空间来展示完整信息,让卡片看起来更加丰富。</p>
<footer>2024-01-16</footer>
</article>
<article class="card">
<h3>中等长度标题</h3>
<p>中等长度的内容。</p>
<footer>2024-01-17</footer>
</article>
</div>
<style>
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-template-rows: auto auto auto; /* 标题行、内容行、底部行 */
gap: 20px;
}
.card {
display: grid;
grid-template-rows: subgrid; /* 继承父网格的行定义 */
grid-row: span 3; /* 每张卡片跨越三行 */
background: white;
border-radius: 8px;
padding: 0 20px; /* 水平内边距 */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card h3 {
/* 自动对齐到第一行 */
margin: 20px 0 0;
}
.card p {
/* 自动对齐到第二行 */
margin: 10px 0;
}
.card footer {
/* 自动对齐到第三行 */
margin: 0 0 20px;
color: #666;
font-size: 14px;
}
</style>
这个例子中,所有卡片的标题在水平方向上对齐,内容区域对齐,底部信息也对齐,即使各卡片的内容长度不同。
子网格的注意事项
1. 没有隐式网格
在子网格维度上不会自动创建隐式轨道。如果子网格内的项目超出定义的轨道数量,它们会被放入最后一个轨道:
.item {
display: grid;
grid-template-columns: subgrid;
grid-template-rows: subgrid; /* 同时使用子网格 */
}
/* 如果有超出轨道数量的项目,它们会溢出 */
如果需要自动创建轨道,应该只在需要的维度使用子网格:
.item {
display: grid;
grid-template-columns: subgrid;
grid-auto-rows: minmax(100px, auto); /* 行使用隐式网格 */
}
2. 行号重新开始
子网格内的行号从 1 开始,不继承父网格的行号:
/* 父网格 */
.parent { grid-template-columns: 1fr 1fr 1fr; }
/* 子网格跨越父网格的第 2-4 列 */
.item {
grid-column: 2 / 5;
grid-template-columns: subgrid;
}
/* 子网格内的项目从 1 开始计数 */
.subitem {
grid-column: 1; /* 对应父网格的第 2 列 */
}
这实际上是一个优点:组件可以在父网格的任何位置使用,内部的行号始终一致。
3. 浏览器兼容性
子网格在现代浏览器中已得到良好支持:
| 浏览器 | 支持版本 |
|---|---|
| Chrome | 117+ |
| Firefox | 71+ |
| Safari | 16+ |
| Edge | 117+ |
子网格最佳实践
1. 组件化设计
子网格非常适合创建可复用的组件:
/* 可放置在任何位置的卡片组件 */
.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
}
2. 表单布局
让表单标签和输入框完美对齐:
.form-row {
display: grid;
grid-template-columns: auto 1fr;
gap: 10px;
}
.form-row:has(.error) {
grid-template-rows: auto auto; /* 为错误信息预留空间 */
}
.form-row .error {
grid-column: 2;
}
3. 结合容器查询
子网格与容器查询配合,创建真正独立的响应式组件:
.card {
container-type: inline-size;
display: grid;
grid-template-rows: subgrid;
}
@container (min-width: 400px) {
.card {
grid-template-columns: subgrid;
}
}
Flexbox vs Grid 选择指南
| 特性 | Flexbox | Grid |
|---|---|---|
| 维度 | 一维(行或列) | 二维(行和列) |
| 内容驱动 | 是,适合内容决定布局 | 否,先定义布局再放内容 |
| 对齐 | 强大的对齐能力 | 同样强大 |
| 适用场景 | 导航栏、卡片列表、居中 | 页面布局、表格、相册 |
选择建议
/* 一维布局用 Flexbox */
.navbar { display: flex; }
.card-list { display: flex; flex-wrap: wrap; }
.center { display: flex; justify-content: center; align-items: center; }
/* 二维布局用 Grid */
.page-layout { display: grid; }
.gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; }
组合使用
实际项目中,两者常结合使用:
<div class="page">
<header class="header">
<nav class="nav"><!-- 这里用 Flexbox --></nav>
</header>
<main class="content">
<div class="card-grid"><!-- 这里用 Grid -->
<div class="card"><!-- 卡片内部用 Flexbox --></div>
</div>
</main>
</div>
浮动布局
浮动(float)是早期的布局方式,虽然现代布局推荐使用 Flexbox 和 Grid,但理解浮动仍然重要,因为很多现有代码仍在使用。
float 属性
.element {
float: none; /* 默认,不浮动 */
float: left; /* 左浮动 */
float: right; /* 右浮动 */
}
清除浮动
当容器内只有浮动元素时,容器高度会塌陷。需要清除浮动:
/* 方法1:clearfix 伪元素 */
.clearfix::after {
content: "";
display: table;
clear: both;
}
/* 方法2:创建 BFC(推荐) */
.container {
display: flow-root;
}
文字环绕
浮动最原始的用途是让文字环绕图片:
<img src="photo.jpg" class="float-img" alt="照片">
<p>这段文字会环绕在图片周围...</p>
<style>
.float-img {
float: left;
margin: 0 20px 10px 0;
}
</style>
锚点定位 Anchor Positioning
锚点定位(Anchor Positioning)是 CSS 2024 年新增的重要特性,它允许将一个元素"拴"在另一个元素上,使定位元素的位置和大小可以相对于其锚点元素来设置。这解决了长期以来需要 JavaScript 才能实现的元素关联定位问题。
为什么需要锚点定位?
在 Web 开发中,经常需要将一个元素相对于另一个元素定位,例如:
- 表单控件旁边的错误提示
- 按钮旁边的工具提示(Tooltip)
- 下拉菜单或弹出框
- 设置对话框
传统方式需要使用 JavaScript 来计算位置并动态更新,既复杂又影响性能。CSS 锚点定位提供了一种纯 CSS 的声明式解决方案。
基本概念
锚点定位涉及两种元素:
- 锚点元素(Anchor Element):作为定位参考的元素
- 锚点定位元素(Anchor-Positioned Element):相对于锚点元素定位的元素
关联锚点与定位元素
显式关联
使用 anchor-name 属性声明锚点元素,使用 position-anchor 属性将定位元素绑定到锚点:
<div class="anchor">锚点元素</div>
<div class="infobox">定位元素</div>
<style>
.anchor {
anchor-name: --my-anchor;
width: fit-content;
padding: 10px 20px;
background: #3498db;
color: white;
}
.infobox {
position: fixed;
position-anchor: --my-anchor;
/* 现在已关联,但还未定位 */
padding: 10px;
background: #2ecc71;
color: white;
}
</style>
隐式关联
某些 HTML 特性会自动创建隐式锚点关联:
- Popover API:使用
popovertarget属性关联弹出框与触发按钮 - 自定义
<select>元素:下拉选择框与其触发器
<button popovertarget="menu">打开菜单</button>
<div popover id="menu">
<!-- 这个弹出框自动与按钮建立隐式锚点关联 -->
<ul>
<li>选项一</li>
<li>选项二</li>
</ul>
</div>
定位元素:anchor() 函数
使用 anchor() 函数在 inset 属性中设置定位元素相对于锚点的位置:
/* 语法 */
anchor(<anchor-name> <anchor-side>, <fallback>)
/* 参数说明 */
/* <anchor-name>: 锚点名称,可省略(使用默认锚点) */
/* <anchor-side>: 锚点的哪一侧 */
/* <fallback>: 回退值 */
anchor-side 值:
| 值 | 说明 |
|---|---|
top | 锚点顶边 |
bottom | 锚点底边 |
left | 锚点左边 |
right | 锚点右边 |
start | 锚点逻辑起始边 |
end | 锚点逻辑结束边 |
center | 锚点中心 |
<percentage> | 锚点轴上的百分比位置 |
示例:工具提示
<button class="anchor-button">悬停显示提示</button>
<div class="tooltip">这是一个工具提示</div>
<style>
.anchor-button {
anchor-name: --button-anchor;
padding: 10px 20px;
}
.tooltip {
position: fixed;
position-anchor: --button-anchor;
/* 定位在锚点下方 */
top: anchor(bottom);
left: anchor(center);
transform: translateX(-50%);
margin-top: 8px; /* 与锚点的间距 */
padding: 8px 12px;
background: #333;
color: white;
border-radius: 4px;
font-size: 14px;
/* 默认隐藏 */
opacity: 0;
visibility: hidden;
transition: opacity 0.2s, visibility 0.2s;
}
.anchor-button:hover + .tooltip {
opacity: 1;
visibility: visible;
}
</style>
示例:内联计算
.tooltip {
/* 使用 calc() 添加间距 */
top: calc(anchor(bottom) + 10px);
left: anchor(center);
}
position-area 属性
position-area 提供了一种更简单的方式来定位元素,基于一个 3×3 的网格:
┌─────────┬─────────┬─────────┐
│top left │top │top right│
│ │center │ │
├─────────┼─────────┼─────────┤
│left │ center │ right │
│center │ │center │
├─────────┼─────────┼─────────┤
│bottom │bottom │bottom │
│left │center │right │
└─────────┴─────────┴─────────┘
常用值:
/* 放置在锚点上方 */
.tooltip { position-area: top; }
/* 放置在锚点下方 */
.tooltip { position-area: bottom; }
/* 放置在锚点左侧 */
.tooltip { position-area: left; }
/* 放置在锚点右侧 */
.tooltip { position-area: right; }
/* 放置在锚点右上角 */
.tooltip { position-area: top right; }
/* 放置在锚点下方,横跨整个宽度 */
.tooltip { position-area: bottom span-all; }
完整示例:
<style>
.menu-trigger {
anchor-name: --menu-anchor;
}
.dropdown-menu {
position: fixed;
position-anchor: --menu-anchor;
position-area: bottom span-all;
margin-top: 4px;
padding: 8px 0;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.dropdown-menu li {
padding: 8px 16px;
list-style: none;
}
.dropdown-menu li:hover {
background: #f5f5f5;
}
</style>
锚点尺寸:anchor-size() 函数
使用 anchor-size() 函数可以根据锚点元素的尺寸来设置定位元素的尺寸:
/* 语法 */
anchor-size(<anchor-name> <anchor-size>, <fallback>)
/* 参数说明 */
/* <anchor-name>: 锚点名称,可省略 */
/* <anchor-size>: width 或 height */
/* <fallback>: 回退值 */
示例:下拉菜单宽度匹配按钮
.dropdown-menu {
position: fixed;
position-anchor: --button-anchor;
position-area: bottom;
/* 宽度等于锚点宽度 */
width: anchor-size(width);
margin-top: 4px;
background: white;
}
/* 或者设置稍宽一些 */
.dropdown-menu-wide {
width: calc(anchor-size(width) + 20px);
}
居中对齐:anchor-center
在 justify-self、align-self 等属性中使用 anchor-center 值,可以将定位元素相对于锚点居中:
.tooltip {
position: fixed;
position-anchor: --my-anchor;
top: anchor(bottom);
justify-self: anchor-center; /* 水平居中于锚点 */
margin-top: 8px;
padding: 8px 12px;
background: #333;
color: white;
}
回退定位:position-try
当定位元素在默认位置会溢出视口时,可以使用 position-try 指定备选位置:
.tooltip {
position: fixed;
position-anchor: --my-anchor;
position-area: bottom;
/* 尝试顺序:底部 → 顶部 → 左侧 → 右侧 */
position-try-fallbacks: flip-block, flip-inline;
position-try-order: most-height;
}
position-try-fallbacks 值:
| 值 | 说明 |
|---|---|
flip-block | 翻转到锚点的另一侧(上下翻转) |
flip-inline | 翻转到锚点的另一侧(左右翻转) |
flip-start | 翻转到起始边 |
flip-end | 翻转到结束边 |
锚点作用域:anchor-scope
当页面中有多个同名的锚点时,使用 anchor-scope 限制锚点名称的作用范围:
<style>
.card {
/* 限制锚点名称只在本卡片内有效 */
anchor-scope: --card-anchor;
}
.card-anchor {
anchor-name: --card-anchor;
}
.card-tooltip {
position: fixed;
position-anchor: --card-anchor;
/* 只会关联到同一卡片内的锚点 */
}
</style>
<div class="card">
<div class="card-anchor">锚点</div>
<div class="card-tooltip">提示</div>
</div>
<div class="card">
<div class="card-anchor">锚点</div>
<div class="card-tooltip">提示</div>
</div>
完整示例:工具提示组件
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<style>
.tooltip-container {
position: relative;
}
.tooltip-trigger {
anchor-name: --trigger;
padding: 8px 16px;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.tooltip {
position: fixed;
position-anchor: --trigger;
position-area: top;
/* 水平居中 */
justify-self: anchor-center;
margin-bottom: 8px;
padding: 8px 12px;
background: #333;
color: white;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
/* 回退:如果上方空间不足,尝试下方 */
position-try-fallbacks: flip-block;
/* 默认隐藏 */
opacity: 0;
visibility: hidden;
transition: opacity 0.2s;
}
/* 小箭头 */
.tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: #333;
}
.tooltip-trigger:hover + .tooltip,
.tooltip-trigger:focus + .tooltip {
opacity: 1;
visibility: visible;
}
</style>
</head>
<body>
<button class="tooltip-trigger">悬停显示提示</button>
<div class="tooltip">这是一个工具提示信息</div>
</body>
</html>
浏览器支持
锚点定位是较新的 CSS 特性,需要检查浏览器支持:
@supports (anchor-name: --test) {
/* 支持锚点定位 */
.tooltip {
position-anchor: --my-anchor;
}
}
锚点定位现在已在所有主流浏览器中得到支持:
- Chrome 125+
- Edge 125+
- Safari 15.4+
- Firefox 147+
对于需要支持旧版本浏览器的情况,可以使用 JavaScript 作为回退方案。
布局最佳实践
1. 优先使用现代布局
/* 推荐 */
.container {
display: flex;
gap: 20px;
}
/* 避免 */
.container {
overflow: hidden;
}
.container .item {
float: left;
margin-right: 20px;
}
2. 使用 gap 替代 margin
/* 推荐 */
.flex-container {
display: flex;
gap: 20px;
}
/* 避免 */
.flex-container .item {
margin-right: 20px;
}
.flex-container .item:last-child {
margin-right: 0;
}
3. 响应式布局优先移动端
/* 移动优先:从小屏幕开始 */
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 15px;
}
@media (min-width: 768px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
4. 避免过度嵌套
/* 不推荐:深层嵌套 */
.page .content .main .article .text { }
/* 推荐:扁平选择器 */
.article-text { }
5. 使用 CSS 变量管理布局
:root {
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--sidebar-width: 250px;
--header-height: 60px;
}
.sidebar {
width: var(--sidebar-width);
}
.header {
height: var(--header-height);
}
小结
本章系统学习了 CSS 布局的核心知识:
- 定位 Position:static、relative、absolute、fixed、sticky 五种定位方式的特点和使用场景
- BFC 块格式化上下文:理解 BFC 的创建条件、三大核心特性(包含浮动、排除浮动、阻止外边距合并)及其实践应用
- 层叠上下文:理解层叠上下文的创建条件和层级规则,掌握 z-index 的正确使用
- Flexbox 弹性布局:一维布局系统,适合处理行或列的元素排列
- Grid 网格布局:二维布局系统,适合复杂的页面整体布局
- 浮动布局:传统布局方式,主要用于文字环绕
- 锚点定位 Anchor Positioning:CSS 2024 新特性,实现元素间的关联定位,适用于工具提示、下拉菜单等场景
练习
- 使用 position 实现一个固定在页面右下角的"返回顶部"按钮
- 解释为什么以下代码中外边距会合并,并用 BFC 解决这个问题
- 使用 Flexbox 实现一个响应式导航栏
- 使用 Grid 实现一个包含 header、sidebar、main、footer 的经典页面布局
- 创建一个响应式卡片网格,在移动端显示 1 列,平板 2 列,桌面 3 列
- 使用锚点定位实现一个工具提示组件,当按钮悬停时显示提示文字