跳到主要内容

CSS 布局

CSS 布局是网页设计的核心,决定了元素在页面上的位置和排列方式。本章将系统介绍 CSS 布局的核心概念和技术,从基础的定位到现代的 Flexbox 和 Grid 布局。

布局基础概念

在深入具体技术之前,我们需要理解几个核心概念,这些概念是理解 CSS 布局行为的关键。

正常文档流

正常文档流(Normal Flow)是页面元素的默认布局方式。在正常流中,块级元素从上到下依次排列,行内元素从左到右依次排列。

<div>块级元素1</div>
<div>块级元素2</div>
<span>行内元素1</span>
<span>行内元素2</span>

脱离正常文档流的方式有:

  • 浮动(float
  • 绝对定位(position: absolutefixed

包含块

包含块(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(静态定位,默认值)

元素按照正常文档流布局,toprightbottomleftz-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
  • 默认宽度和高度由内容决定
  • 可以通过设置 topbottom(或 leftright)同时拉伸元素

常见用途:

  • 弹窗、下拉菜单
  • 图标叠加在图片上
  • 工具提示
<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;
}

工作原理:

  1. 元素首先按正常文档流定位
  2. 当滚动使其到达指定阈值(如 top: 0),元素变为固定定位
  3. 元素会"粘"在其包含块内,当包含块滚出视口时,元素也会随之滚出

重要特性:

  • 需要指定至少一个定位属性(toprightbottomleft
  • 始终创建新的层叠上下文
  • 需要理解包含块的概念——元素只能在其包含块范围内"粘"
<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: hiddenoverflow: autooverflow: 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
floatnone浮动元素
positionabsolutefixed绝对定位元素
displayinline-block行内块元素
displayflow-root专门用于创建 BFC(推荐)
overflowvisibleoverflow: autohidden
displayflex / inline-flex 的子项Flex 项目的子元素
displaygrid / inline-grid 的子项Grid 项目的子元素
containlayoutcontentpaintCSS 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>页面的根层叠上下文
positionstaticz-indexauto定位元素
positionfixedsticky固定/粘性定位始终创建
opacity 小于 1透明度变化
transformnone变换属性
filternone滤镜效果
will-change 指定相关属性性能优化提示
isolation: isolate显式创建隔离
Flex/Grid 子项且 z-indexauto弹性/网格项目

层叠上下文的层级规则

核心规则: 子元素的 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>

层叠顺序

在同一层叠上下文中,元素的层叠顺序从低到高为:

  1. 层叠上下文的背景和边框
  2. z-index 的子元素
  3. 块级元素(正常文档流)
  4. 浮动元素
  5. 行内元素
  6. z-index: 0auto 的子元素
  7. 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. 浏览器兼容性

子网格在现代浏览器中已得到良好支持:

浏览器支持版本
Chrome117+
Firefox71+
Safari16+
Edge117+

子网格最佳实践

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 选择指南

特性FlexboxGrid
维度一维(行或列)二维(行和列)
内容驱动是,适合内容决定布局否,先定义布局再放内容
对齐强大的对齐能力同样强大
适用场景导航栏、卡片列表、居中页面布局、表格、相册

选择建议

/* 一维布局用 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-selfalign-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 布局的核心知识:

  1. 定位 Position:static、relative、absolute、fixed、sticky 五种定位方式的特点和使用场景
  2. BFC 块格式化上下文:理解 BFC 的创建条件、三大核心特性(包含浮动、排除浮动、阻止外边距合并)及其实践应用
  3. 层叠上下文:理解层叠上下文的创建条件和层级规则,掌握 z-index 的正确使用
  4. Flexbox 弹性布局:一维布局系统,适合处理行或列的元素排列
  5. Grid 网格布局:二维布局系统,适合复杂的页面整体布局
  6. 浮动布局:传统布局方式,主要用于文字环绕
  7. 锚点定位 Anchor Positioning:CSS 2024 新特性,实现元素间的关联定位,适用于工具提示、下拉菜单等场景

练习

  1. 使用 position 实现一个固定在页面右下角的"返回顶部"按钮
  2. 解释为什么以下代码中外边距会合并,并用 BFC 解决这个问题
  3. 使用 Flexbox 实现一个响应式导航栏
  4. 使用 Grid 实现一个包含 header、sidebar、main、footer 的经典页面布局
  5. 创建一个响应式卡片网格,在移动端显示 1 列,平板 2 列,桌面 3 列
  6. 使用锚点定位实现一个工具提示组件,当按钮悬停时显示提示文字