CSS 是前端开发的三大基础技术之一。HTML 负责页面结构,CSS 负责页面表现,JavaScript 负责页面行为。本文将从基础语法、选择器、盒模型开始,逐步讲解 Flexbox、Grid、响应式设计、动画、CSS 变量、容器查询、层叠层、原生嵌套、性能优化和工程化实践。
1. CSS 是什么
CSS 的全称是 Cascading Style Sheets,中文通常翻译为"层叠样式表"。
CSS 用于描述 HTML 或 XML 文档在屏幕、纸张等媒介上的显示效果,包括:
- 文字颜色和字体
- 元素大小和间距
- 页面布局
- 背景、边框和阴影
- 响应式适配
- 过渡和动画
- 深色模式
- 打印样式
一个简单的页面可以分成三层:
text
HTML:页面结构
CSS:页面样式
JavaScript:页面交互
例如下面的 HTML:
html
<h1 class="title">学习 CSS</h1>
配合 CSS:
css
.title {
color: #2563eb;
font-size: 32px;
text-align: center;
}
浏览器会把标题显示成蓝色、32 像素并居中。
1.1 CSS 的发展方式
人们经常使用"CSS3"表示现代 CSS,但 CSS 并不是在 CSS3 之后整体升级成 CSS4。现代 CSS 已经被拆分为多个独立模块,例如:
- CSS Color
- CSS Grid Layout
- CSS Flexible Box Layout
- CSS Selectors
- CSS Cascading and Inheritance
- CSS Containment
- CSS Transforms
- CSS Animations
不同模块会按照自己的节奏发展。
2. CSS 的基本语法
2.1 CSS 规则结构
一条完整的 CSS 规则由选择器和声明块组成:
css
选择器 {
属性名: 属性值;
}
例如:
css
.card {
width: 320px;
padding: 24px;
background-color: white;
border-radius: 12px;
}
其中:
.card是选择器width、padding是属性320px、24px是属性值- 每条声明通常以分号结束
2.2 CSS 注释
CSS 使用下面的注释语法:
css
/* 这是一段 CSS 注释 */
CSS 不支持 JavaScript 风格的 // 单行注释。
2.3 在 HTML 中引入 CSS
方式一:行内样式
html
<p style="color: red; font-size: 18px;">这是一段文字</p>
优点是简单直接,缺点是难以复用和维护,一般不建议在大型项目中大量使用。
方式二:内部样式表
html
<head>
<style>
p {
color: red;
font-size: 18px;
}
</style>
</head>
适合简单演示或者只有一个 HTML 文件的小页面。
方式三:外部样式表
html
<head>
<link rel="stylesheet" href="./style.css">
</head>
style.css:
css
p {
color: red;
font-size: 18px;
}
外部样式表具有以下优点:
- HTML 与样式分离
- 多个页面可以复用
- 更方便维护
- 浏览器可以缓存 CSS 文件
在实际项目中通常优先使用外部样式表。
2.4 @import
CSS 也可以使用 @import 引入其他样式表:
css
@import url("./reset.css");
@import url("./theme.css");
body {
margin: 0;
}
@import 必须出现在普通样式规则之前。一般页面主样式仍建议通过 HTML 的 <link> 标签加载,由构建工具处理项目内部的样式依赖。
3. CSS 选择器
选择器用于确定一条 CSS 规则应该应用到哪些元素。
3.1 通配选择器
css
* {
box-sizing: border-box;
}
* 会匹配所有元素。
3.2 元素选择器
css
p {
line-height: 1.8;
}
匹配页面中的所有 <p> 元素。
3.3 类选择器
css
.button {
padding: 10px 20px;
}
对应 HTML:
html
<button class="button">提交</button>
类选择器可以被多个元素复用,是日常开发中最常用的选择器之一。
3.4 ID 选择器
css
#header {
height: 64px;
}
对应 HTML:
html
<header id="header"></header>
一个页面中的 ID 应当保持唯一。由于 ID 选择器优先级较高,不利于样式覆盖,因此组件样式通常更推荐使用类选择器。
3.5 属性选择器
css
input[type="email"] {
border-color: #2563eb;
}
a[target="_blank"] {
color: #7c3aed;
}
[data-state="active"] {
background-color: #dbeafe;
}
常见属性选择器:
css
/* 存在某个属性 */
[disabled] {
cursor: not-allowed;
}
/* 属性值完全相等 */
[type="text"] {
background: white;
}
/* 属性值以某段文字开头 */
[href^="https"] {
color: green;
}
/* 属性值以某段文字结尾 */
[href$=".pdf"] {
color: red;
}
/* 属性值中包含某段文字 */
[class*="icon-"] {
display: inline-block;
}
3.6 后代选择器
css
.article p {
line-height: 1.8;
}
匹配 .article 内部任意层级的 <p>。
3.7 子元素选择器
css
.menu > li {
list-style: none;
}
只匹配 .menu 的直接子元素 <li>。
3.8 相邻兄弟选择器
css
h2 + p {
margin-top: 8px;
}
匹配紧跟在 <h2> 后面的第一个 <p>。
3.9 后续兄弟选择器
css
h2 ~ p {
color: #475569;
}
匹配与 <h2> 同级且位于它后面的所有 <p>。
3.10 选择器分组
css
h1,
h2,
h3 {
font-family: system-ui, sans-serif;
}
可以把相同的样式同时应用给多个选择器。
3.11 组合选择器示例
css
.article > p:first-of-type {
font-size: 18px;
color: #334155;
}
.form input[type="text"]:focus {
border-color: #2563eb;
outline: 3px solid rgb(37 99 235 / 20%);
}
选择器应该尽量简洁。过长的选择器会增加耦合,让 HTML 结构稍有变化就导致样式失效。
4. 层叠、优先级与继承
CSS 中的"C"代表 Cascading,也就是层叠。
当多个规则同时设置一个元素的同一个属性时,浏览器必须判断最终使用哪个值。W3C CSS Cascading and Inheritance Level 5 规定了声明值如何经过层叠、默认值和计算过程变成元素最终使用的样式。
4.1 层叠判断不只是优先级
可以把常见判断过程简化为:
- 声明是否适用于当前元素
- 来源和
!important - 所在的层叠层
- 选择器优先级
- 作用域接近程度
- 代码出现顺序
对于普通业务代码,最常遇到的是选择器优先级和代码顺序。
4.2 选择器优先级
可以把常见选择器优先级记成三个部分:
text
ID 选择器数量 - 类、属性、伪类数量 - 元素、伪元素数量
例如:
css
/* 0-0-1 */
p {
color: black;
}
/* 0-1-0 */
.text {
color: blue;
}
/* 1-0-0 */
#message {
color: red;
}
如果它们匹配的是同一个元素,通常 ID 选择器的规则会胜出。
下面的选择器优先级为 0-2-1:
css
.card .title:hover {
color: blue;
}
其中:
.card是一个类选择器.title是一个类选择器:hover是一个伪类- 这里没有元素选择器,因此准确来说是
0-3-0
4.3 同等优先级看出现顺序
css
.title {
color: blue;
}
.title {
color: red;
}
两条规则优先级相同,后面的 color: red 生效。
4.4 行内样式
html
<p class="message" style="color: red;">提示信息</p>
行内样式在普通作者样式中具有较高优先级,但仍然可能受到 !important、动画或不同样式来源规则的影响。
实际项目不应单纯依靠"优先级数字"理解完整层叠过程。
4.5 !important
css
.button {
color: white !important;
}
!important 会改变声明在层叠过程中的重要性。
不建议把它当成常规覆盖方案,因为它会让后续维护越来越困难。更好的处理方式是:
- 降低选择器复杂度
- 调整 CSS 加载顺序
- 使用层叠层
- 重新设计组件状态
- 使用更明确的类名
4.6 继承
部分 CSS 属性会从父元素继承,例如:
colorfont-familyfont-sizeline-heighttext-alignvisibility
部分属性通常不会继承,例如:
widthheightmarginpaddingborderbackgroundposition
示例:
css
body {
color: #334155;
font-family: system-ui, sans-serif;
}
页面中的大多数文本元素会继承这些设置。
4.7 CSS 全局关键字
css
.example {
color: inherit;
margin: initial;
border: unset;
background: revert;
}
常用关键字:
| 关键字 | 含义 |
|---|---|
inherit |
使用父元素的计算值 |
initial |
使用该属性规范定义的初始值 |
unset |
可继承属性按继承处理,否则使用初始值 |
revert |
回退到较低层叠来源的结果 |
revert-layer |
回退当前层叠层造成的影响 |
注意:initial 不等于浏览器样式表为某个 HTML 标签设置的默认表现。
5. CSS 的颜色与单位
5.1 颜色表示方式
颜色名称
css
.title {
color: red;
}
十六进制
css
.title {
color: #2563eb;
}
带透明度的八位十六进制:
css
.overlay {
background-color: #00000080;
}
RGB
css
.title {
color: rgb(37 99 235);
}
.overlay {
background-color: rgb(15 23 42 / 60%);
}
HSL
css
.title {
color: hsl(221 83% 53%);
}
currentColor
currentColor 表示元素当前的 color 值:
css
.button {
color: #2563eb;
border: 1px solid currentColor;
}
修改文字颜色时,边框也会自动变化。
5.2 绝对长度单位
css
.box {
width: 300px;
border-width: 1px;
}
网页开发中最常用的绝对长度单位是 px。
CSS 像素是一个逻辑单位,不一定等同于设备上的一个物理像素。
5.3 相对单位
| 单位 | 含义 |
|---|---|
% |
相对于相关参考值 |
em |
通常相对于当前元素的字体尺寸 |
rem |
相对于根元素的字体尺寸 |
vw |
视口宽度的 1% |
vh |
视口高度的 1% |
dvh |
动态视口高度的 1% |
svh |
小视口高度的 1% |
lvh |
大视口高度的 1% |
ch |
近似基于当前字体中数字 0 的宽度 |
fr |
Grid 布局中的剩余空间份额 |
示例:
css
html {
font-size: 16px;
}
.article {
width: min(90%, 72rem);
margin-inline: auto;
}
.article h1 {
font-size: 2.5rem;
}
.article p {
max-width: 70ch;
}
5.4 如何选择单位
常见建议:
- 边框可以使用
px - 字体和间距可使用
rem - 局部尺寸可使用
% - Grid 剩余空间使用
fr - 全屏区域可使用
dvh - 流式尺寸可以使用
clamp() - 不要机械地把所有
px替换成rem
6. CSS 盒模型
浏览器通常会把元素渲染成一个矩形盒子。
盒模型从内到外包括:
text
content → padding → border → margin
6.1 标准盒模型
css
.box {
width: 300px;
padding: 20px;
border: 5px solid #2563eb;
}
默认情况下:
css
box-sizing: content-box;
实际占用宽度为:
text
300 + 20 × 2 + 5 × 2 = 350px
外边距不属于盒子自身宽度,但会影响元素与周围内容之间的距离。
6.2 border-box
css
.box {
box-sizing: border-box;
width: 300px;
padding: 20px;
border: 5px solid #2563eb;
}
此时声明的 width: 300px 已经包含内容区、内边距和边框。
实际项目通常会使用:
css
*,
*::before,
*::after {
box-sizing: border-box;
}
6.3 外边距
css
.card {
margin: 20px;
}
简写规则:
css
/* 上下左右 */
margin: 20px;
/* 上下、左右 */
margin: 20px 30px;
/* 上、左右、下 */
margin: 10px 20px 30px;
/* 上、右、下、左 */
margin: 10px 20px 30px 40px;
padding 的简写规则相同。
6.4 水平居中
具有确定或受约束宽度的块级元素可以使用:
css
.container {
width: min(90%, 1200px);
margin-inline: auto;
}
6.5 外边距折叠
普通文档流中,部分相邻块级元素的垂直外边距可能发生折叠:
css
.first {
margin-bottom: 30px;
}
.second {
margin-top: 20px;
}
最终间距可能是 30px,而不是 50px。
Flex、Grid 项目之间的外边距不会按照普通块级布局的方式折叠。现代布局中更推荐通过父容器的 gap 控制项目间距。
6.6 min-width 和 max-width
css
.card {
width: 100%;
max-width: 420px;
}
.sidebar {
min-width: 240px;
}
响应式设计中,通常比固定宽度更常使用:
css
width: 100%;
max-width: 1200px;
7. 文本、字体与背景
7.1 字体设置
css
body {
font-family:
Inter,
"PingFang SC",
"Microsoft YaHei",
system-ui,
sans-serif;
}
浏览器会按照顺序寻找可用字体。
7.2 字体大小与行高
css
.article {
font-size: 16px;
line-height: 1.75;
}
无单位行高会根据元素自己的字体大小计算,并且在继承时通常更灵活。
7.3 字体粗细
css
.title {
font-weight: 700;
}
.description {
font-weight: 400;
}
常见值:
text
100、200、300、400、500、600、700、800、900
具体效果取决于字体是否包含对应字重。
7.4 文本对齐与装饰
css
.title {
text-align: center;
}
.link {
text-decoration: none;
}
.price {
text-decoration: line-through;
}
7.5 文本溢出省略
单行省略:
css
.single-line {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
多行省略常见写法:
css
.multi-line {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
使用多行截断时,要确认目标浏览器兼容性,并确保用户仍有方式读取完整内容。
7.6 自动换行
css
.content {
overflow-wrap: anywhere;
}
这对于长链接、哈希值和连续英文字符非常有用。
7.7 背景
css
.hero {
background-color: #0f172a;
background-image: url("./images/banner.jpg");
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
渐变背景:
css
.hero {
background:
linear-gradient(
135deg,
rgb(37 99 235 / 95%),
rgb(124 58 237 / 90%)
);
}
多层背景:
css
.hero {
background:
linear-gradient(rgb(15 23 42 / 65%), rgb(15 23 42 / 65%)),
url("./images/banner.jpg") center / cover no-repeat;
}
7.8 object-fit
图片在固定区域中可以使用:
css
.avatar {
width: 96px;
aspect-ratio: 1;
object-fit: cover;
border-radius: 50%;
}
常用值:
cover:填满容器,可能裁剪contain:完整显示,可能留白fill:拉伸填满none:保持原始尺寸
8. 文档流与 display
8.1 普通文档流
如果没有特殊布局规则:
- 块级元素通常从上到下排列
- 行内元素通常在一行内排列
- 元素会根据内容和可用空间占据位置
理解普通文档流非常重要。很多布局问题并不需要使用定位解决。
8.2 常用 display 值
css
.block {
display: block;
}
.inline {
display: inline;
}
.inline-block {
display: inline-block;
}
.flex {
display: flex;
}
.grid {
display: grid;
}
.hidden {
display: none;
}
8.3 block
块级盒子通常:
- 独占一行
- 默认宽度会填充可用空间
- 可以设置宽高和外边距
8.4 inline
行内盒子通常:
- 与文本在一行内排列
- 宽高主要由内容决定
- 上下方向的尺寸行为与块级盒子不同
8.5 inline-block
inline-block 同时具有部分行内和块级特性:
- 可以与其他元素排列在一行
- 可以设置宽高
- 可以设置完整的内外边距
8.6 隐藏元素
css
.hidden {
display: none;
}
元素不会显示,也不占据布局空间,并且通常不会进入无障碍树。
css
.invisible {
visibility: hidden;
}
元素不可见,但仍然占据布局空间。
css
.transparent {
opacity: 0;
}
元素完全透明,但仍占据空间,也可能继续接收鼠标或键盘交互。
如果需要隐藏但保留给屏幕阅读器,可以使用专门的视觉隐藏样式:
css
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip-path: inset(50%);
}
9. 定位与层叠上下文
9.1 position: static
css
.element {
position: static;
}
这是大多数元素的默认定位方式。
9.2 相对定位
css
.element {
position: relative;
top: 10px;
left: 20px;
}
元素仍保留原来的布局空间,只在视觉上发生偏移。
更常见的用途是给绝对定位子元素建立包含块:
css
.card {
position: relative;
}
.card-badge {
position: absolute;
top: 12px;
right: 12px;
}
9.3 绝对定位
css
.tooltip {
position: absolute;
top: 100%;
left: 0;
}
绝对定位元素会脱离普通文档流。它通常相对于最近的、能建立定位包含块的祖先进行定位。
9.4 固定定位
css
.back-to-top {
position: fixed;
right: 24px;
bottom: 24px;
}
固定定位通常相对于视口定位,但某些祖先的变换、过滤和包含属性可能改变它的包含块。
9.5 粘性定位
css
.navbar {
position: sticky;
top: 0;
z-index: 10;
}
sticky 会在普通布局位置和指定边界之间切换。
粘性定位失效时,可以检查:
- 是否设置了
top、bottom等偏移值 - 祖先元素是否设置了
overflow - 容器是否有足够的滚动距离
- 粘性元素是否和容器一样高
- 滚动容器是否符合预期
9.6 z-index
css
.modal {
position: fixed;
z-index: 1000;
}
z-index 不是一个全局绝对排名。元素会先在自己的层叠上下文中排序。
可能创建层叠上下文的条件包括:
- 根元素
- 某些带
z-index的定位元素 position: fixedposition: stickyopacity小于1transform不为nonefilter不为noneisolation: isolate- 某些
contain设置
当 z-index: 999999 仍然被遮挡时,应检查祖先元素所在的层叠上下文,而不是继续增大数字。
9.7 建立独立层叠上下文
css
.component {
isolation: isolate;
}
这样可以让组件内部的 z-index 更容易管理,避免与页面其他区域相互干扰。
10. Flexbox 弹性布局
Flexbox 适合一维布局,即主要沿行或者列的方向排列元素。W3C Flexbox 规范 定义了弹性容器、主轴、交叉轴、伸缩和对齐行为。
10.1 创建弹性容器
css
.container {
display: flex;
}
直接子元素会成为 Flex 项目。
10.2 主轴方向
css
.container {
display: flex;
flex-direction: row;
}
常用值:
rowrow-reversecolumncolumn-reverse
10.3 主轴对齐
css
.container {
display: flex;
justify-content: center;
}
常用值:
flex-startflex-endcenterspace-betweenspace-aroundspace-evenly
10.4 交叉轴对齐
css
.container {
display: flex;
align-items: center;
}
常用值:
stretchflex-startflex-endcenterbaseline
10.5 换行
css
.container {
display: flex;
flex-wrap: wrap;
}
10.6 项目间距
css
.container {
display: flex;
gap: 16px;
}
相比给每个子元素单独设置外边距,gap 更直观,也不会在容器边缘产生多余间距。
10.7 flex 简写
css
.item {
flex: 1 1 240px;
}
依次表示:
text
flex-grow flex-shrink flex-basis
含义:
flex-grow:剩余空间的扩张比例flex-shrink:空间不足时的收缩比例flex-basis:参与伸缩计算前的基础尺寸
常用写法:
css
.item {
flex: 1;
}
通常用于让多个项目平均分配剩余空间。
10.8 单独控制项目
css
.item {
align-self: flex-end;
}
排序:
css
.item-featured {
order: -1;
}
不要为了视觉顺序随意改变 order,因为视觉顺序和 DOM、键盘焦点、屏幕阅读器顺序可能不一致。
10.9 Flex 项目内容溢出
Flex 项目的自动最小尺寸可能导致长文本撑开容器:
css
.content {
min-width: 0;
}
单行省略常见组合:
css
.content {
min-width: 0;
}
.content-title {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
10.10 常见布局
水平垂直居中:
css
.center {
display: flex;
justify-content: center;
align-items: center;
}
左右布局:
css
.header {
display: flex;
align-items: center;
justify-content: space-between;
}
自适应卡片:
css
.card-list {
display: flex;
flex-wrap: wrap;
gap: 24px;
}
.card {
flex: 1 1 280px;
}
11. Grid 网格布局
Grid 适合二维布局,可以同时控制行和列。W3C CSS Grid Layout Level 2 在网格布局基础上定义了 subgrid 等能力。
11.1 创建网格容器
css
.container {
display: grid;
}
11.2 定义列
css
.container {
display: grid;
grid-template-columns: 200px 1fr 1fr;
}
表示:
- 第一列固定为
200px - 第二列和第三列平均分配剩余空间
11.3 repeat()
css
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
等价于:
css
grid-template-columns: 1fr 1fr 1fr;
11.4 设置间距
css
.container {
display: grid;
gap: 24px;
}
也可以分别设置:
css
.container {
row-gap: 16px;
column-gap: 24px;
}
11.5 响应式卡片网格
css
.card-list {
display: grid;
grid-template-columns: repeat(
auto-fit,
minmax(min(100%, 280px), 1fr)
);
gap: 24px;
}
这段代码表示:
- 每列尽量不小于
280px - 一行能放几列就放几列
- 剩余空间平均分配
- 容器小于
280px时不强行溢出
11.6 auto-fit 和 auto-fill
两者都会根据容器空间重复创建列。
可以简单理解为:
auto-fill:尽量保留可容纳的网格轨道auto-fit:会折叠空轨道,让已有项目扩展
11.7 网格项目跨行跨列
css
.featured {
grid-column: span 2;
grid-row: span 2;
}
指定起止线:
css
.header {
grid-column: 1 / -1;
}
-1 表示最后一条显式网格线。
11.8 命名区域
css
.page {
display: grid;
grid-template:
"header header" auto
"sidebar main" 1fr
"footer footer" auto
/ 240px 1fr;
min-height: 100dvh;
}
.header {
grid-area: header;
}
.sidebar {
grid-area: sidebar;
}
.main {
grid-area: main;
}
.footer {
grid-area: footer;
}
11.9 minmax()
css
.layout {
display: grid;
grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
}
第二列使用 minmax(0, 1fr) 可以减少内容因为自动最小尺寸而撑破布局的情况。
11.10 subgrid
父网格:
css
.card-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
卡片内部使用父网格轨道:
css
.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
}
subgrid 适合让不同卡片内部的标题、正文和按钮保持对齐。使用前应结合项目支持范围检查目标浏览器。
11.11 Flexbox 和 Grid 如何选择
使用 Flexbox:
- 主要是一行或一列
- 导航栏
- 按钮组
- 图标与文字
- 简单的水平或垂直对齐
使用 Grid:
- 同时控制行和列
- 卡片网格
- 页面整体布局
- 仪表盘
- 需要跨行跨列的复杂布局
两者不是竞争关系,实际项目经常组合使用。
12. 响应式设计
响应式设计的目标是让页面在手机、平板、桌面端和不同窗口尺寸下都能正常使用。
12.1 设置视口
HTML 中需要设置:
html
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
12.2 移动优先
基础样式先面向小屏幕:
css
.card-list {
display: grid;
grid-template-columns: 1fr;
}
屏幕变宽后增强布局:
css
@media (min-width: 48rem) {
.card-list {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 64rem) {
.card-list {
grid-template-columns: repeat(3, 1fr);
}
}
12.3 不要只按设备名称设计断点
不要简单理解成:
text
手机:固定一个宽度
平板:固定一个宽度
电脑:固定一个宽度
更好的方式是根据内容在什么时候开始拥挤、换行或者失衡来设置断点。
12.4 媒体查询
根据宽度:
css
@media (min-width: 768px) {
.sidebar {
display: block;
}
}
根据横竖屏:
css
@media (orientation: landscape) {
.hero {
min-height: 70vh;
}
}
根据用户深色模式偏好:
css
@media (prefers-color-scheme: dark) {
:root {
--page-bg: #0f172a;
--text-color: #e2e8f0;
}
}
根据用户减少动态效果的偏好:
css
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
12.5 流式排版
css
.hero-title {
font-size: clamp(2rem, 5vw, 4.5rem);
}
含义:
- 最小
2rem - 理想值
5vw - 最大
4.5rem
12.6 响应式容器
css
.container {
width: min(100% - 32px, 1200px);
margin-inline: auto;
}
这样可以同时实现:
- 小屏幕两侧保留
16px - 大屏幕最大宽度为
1200px - 容器水平居中
12.7 响应式图片
css
img {
display: block;
max-width: 100%;
height: auto;
}
对于固定比例的封面:
css
.cover {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
12.8 逻辑属性
传统写法:
css
.card {
margin-left: 16px;
padding-right: 24px;
}
逻辑属性写法:
css
.card {
margin-inline-start: 16px;
padding-inline-end: 24px;
}
常用逻辑属性:
css
.element {
margin-inline: auto;
margin-block: 24px;
padding-inline: 20px;
padding-block: 16px;
border-inline-start: 4px solid #2563eb;
}
逻辑属性能更好地适配从右到左文字和不同书写模式。
13. 容器查询
媒体查询关注视口,容器查询关注组件所在容器的尺寸。
这意味着同一个组件放在主内容区和侧边栏时,可以根据自己的可用空间改变布局,而不是只看整个浏览器窗口。
容器查询是 CSS Containment 模块的重要能力,具体语义可以参考 W3C CSS Containment Module Level 3。
13.1 创建查询容器
css
.feature-section {
container-name: features;
container-type: inline-size;
}
也可以使用简写:
css
.feature-section {
container: features / inline-size;
}
13.2 查询容器宽度
css
.feature-grid {
display: grid;
gap: 16px;
}
@container features (min-width: 48rem) {
.feature-grid {
grid-template-columns: repeat(3, 1fr);
}
}
注意:容器查询主要影响查询容器的后代元素,不能用尺寸查询直接修改作为查询对象的容器自身。
13.3 容器查询单位
常见容器单位:
| 单位 | 含义 |
|---|---|
cqw |
查询容器宽度的 1% |
cqh |
查询容器高度的 1% |
cqi |
查询容器行内轴尺寸的 1% |
cqb |
查询容器块轴尺寸的 1% |
cqmin |
cqi 和 cqb 中较小的值 |
cqmax |
cqi 和 cqb 中较大的值 |
示例:
css
.feature-title {
font-size: clamp(1.25rem, 5cqi, 2.5rem);
}
13.4 媒体查询与容器查询的选择
媒体查询适合:
- 页面整体布局
- 视口级导航切换
- 全局字号和页面边距
- 用户偏好查询
容器查询适合:
- 可复用卡片
- 侧边栏组件
- 独立业务模块
- 同一组件在不同容器中的布局变化
14. 伪类与伪元素
14.1 交互伪类
css
.button:hover {
background-color: #1d4ed8;
}
.button:active {
transform: translateY(1px);
}
.input:focus {
border-color: #2563eb;
}
14.2 :focus-visible
css
.button:focus-visible {
outline: 3px solid rgb(37 99 235 / 35%);
outline-offset: 3px;
}
:focus-visible 主要在浏览器判断需要显示键盘焦点提示时匹配。
不要直接对所有元素设置:
css
*:focus {
outline: none;
}
这样会让键盘用户难以判断当前焦点位置。
14.3 结构伪类
css
.list li:first-child {
font-weight: 700;
}
.list li:last-child {
border-bottom: 0;
}
.list li:nth-child(odd) {
background-color: #f8fafc;
}
.list li:nth-child(3n) {
color: #2563eb;
}
14.4 状态伪类
css
input:disabled {
cursor: not-allowed;
opacity: 0.6;
}
input:checked + label {
color: #2563eb;
}
input:required {
border-inline-start: 3px solid #f59e0b;
}
14.5 :not()
css
.navigation a:not(.active) {
color: #64748b;
}
14.6 :is()
传统写法:
css
.article h1,
.article h2,
.article h3 {
line-height: 1.3;
}
使用 :is():
css
.article :is(h1, h2, h3) {
line-height: 1.3;
}
14.7 :where()
css
:where(.article, .page-content) a {
color: #2563eb;
}
:where() 本身以及参数列表不会增加选择器优先级,适合编写容易覆盖的基础规则。
14.8 :has()
根据子元素或后续关系选择父级:
css
.card:has(.card-image) {
padding-top: 0;
}
.form-field:has(input:focus) {
border-color: #2563eb;
}
.form-field:has(input:invalid:not(:placeholder-shown)) {
border-color: #dc2626;
}
:has()、:is()、:where() 等现代选择器由 W3C Selectors Level 4 定义。使用复杂关系选择器时,应避免匹配范围过大,并结合项目目标浏览器验证支持情况。
14.9 ::before 和 ::after
css
.link::after {
content: " ↗";
}
.title::before {
content: "";
display: inline-block;
width: 4px;
height: 1em;
margin-inline-end: 10px;
background: #2563eb;
border-radius: 999px;
vertical-align: -0.1em;
}
伪元素常用于:
- 装饰线
- 图标背景
- 遮罩
- 气泡箭头
- 不承载核心含义的辅助内容
重要文本和交互信息不应只通过 content 属性生成。
14.10 其他伪元素
css
::selection {
color: white;
background: #2563eb;
}
input::placeholder {
color: #94a3b8;
}
li::marker {
color: #2563eb;
}
15. 变换、过渡与动画
15.1 transform
css
.card {
transform: translateY(-4px);
}
常用变换函数:
css
.example {
transform:
translateX(20px)
translateY(-10px)
rotate(5deg)
scale(1.05);
}
变换通常不会像修改普通布局尺寸那样重新安排周围元素的位置。
15.2 变换原点
css
.element {
transform-origin: center bottom;
}
15.3 过渡
css
.button {
color: white;
background-color: #2563eb;
transform: translateY(0);
transition:
background-color 180ms ease,
transform 180ms ease;
}
.button:hover {
background-color: #1d4ed8;
transform: translateY(-2px);
}
不建议随意使用:
css
transition: all 0.3s;
它可能让本来不需要动画的属性也参与过渡,增加意外行为和性能成本。
15.4 关键帧动画
css
@keyframes pulse {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.08);
opacity: 0.7;
}
}
.notification-dot {
animation: pulse 1.5s ease-in-out infinite;
}
15.5 加载动画
css
@keyframes spin {
to {
transform: rotate(1turn);
}
}
.spinner {
width: 36px;
aspect-ratio: 1;
border: 4px solid #cbd5e1;
border-top-color: #2563eb;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
HTML:
html
<div class="spinner" role="status" aria-label="正在加载"></div>
15.6 动画填充和播放状态
css
.element {
animation-name: fade-in;
animation-duration: 500ms;
animation-timing-function: ease;
animation-delay: 100ms;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: both;
animation-play-state: running;
}
15.7 性能较友好的动画属性
通常优先考虑:
transformopacity
频繁动画以下属性可能引起更多布局或绘制工作:
widthheighttopleftmarginbox-shadowfilter
这不是绝对禁令,最终仍应通过浏览器性能工具测量。
15.8 will-change
css
.dragging-element {
will-change: transform;
}
will-change 只应在确定即将频繁变化时谨慎使用。长期给大量元素设置可能增加内存和合成成本。
16. CSS 变量与计算函数
16.1 定义变量
CSS 自定义属性通常定义在 :root:
css
:root {
--color-primary: #2563eb;
--color-text: #0f172a;
--color-surface: #ffffff;
--radius-md: 12px;
--space-md: 16px;
}
使用变量:
css
.button {
padding: var(--space-md);
color: white;
background-color: var(--color-primary);
border-radius: var(--radius-md);
}
16.2 变量回退值
css
.card {
color: var(--card-color, #334155);
}
如果 --card-color 没有定义,则使用 #334155。
16.3 变量具有层叠和继承能力
css
.theme-dark {
--color-text: #e2e8f0;
--color-surface: #0f172a;
}
.card {
color: var(--color-text);
background: var(--color-surface);
}
放在 .theme-dark 内的卡片会继承新的变量值。
16.4 组件局部变量
css
.button {
--button-bg: #2563eb;
--button-color: white;
color: var(--button-color);
background: var(--button-bg);
}
.button-danger {
--button-bg: #dc2626;
}
这种方式能减少重复代码。
16.5 calc()
css
.sidebar {
height: calc(100dvh - 64px);
}
.main {
width: calc(100% - 280px);
}
16.6 min()
css
.container {
width: min(100% - 32px, 1200px);
}
浏览器会选择较小值。
16.7 max()
css
.page {
padding-inline: max(16px, 4vw);
}
浏览器会选择较大值。
16.8 clamp()
css
.title {
font-size: clamp(2rem, 6vw, 5rem);
}
结构:
text
clamp(最小值, 理想值, 最大值)
16.9 env()
在需要适配安全区域的移动端页面中:
css
.bottom-bar {
padding-bottom: calc(12px + env(safe-area-inset-bottom));
}
17. 现代 CSS 进阶特性
17.1 层叠层 @layer
CSS Cascade Level 5 引入了层叠层,用于明确不同类型样式之间的覆盖顺序。
css
@layer reset, base, components, utilities;
定义样式:
css
@layer reset {
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
}
}
@layer base {
body {
color: #0f172a;
font-family: system-ui, sans-serif;
}
}
@layer components {
.button {
padding: 10px 18px;
border-radius: 8px;
}
}
@layer utilities {
.text-center {
text-align: center;
}
}
对于普通声明,后声明的层通常具有更高的层级优先顺序:
text
reset < base < components < utilities
使用 @layer 的优势:
- 减少优先级竞争
- 明确第三方样式、基础样式和业务样式的关系
- 降低对
!important的依赖 - 让大型项目的覆盖规则更可预测
没有放入层叠层的普通作者样式会参与更高的未分层位置,因此项目应采用一致的层级策略。
17.2 原生 CSS 嵌套
现代 CSS 可以直接嵌套相关规则。语法由 W3C CSS Nesting Module Level 1 定义。
css
.card {
padding: 24px;
border: 1px solid #e2e8f0;
border-radius: 16px;
& .card-title {
margin: 0;
color: #0f172a;
}
&:hover {
border-color: #2563eb;
}
&.featured {
background: #eff6ff;
}
@media (min-width: 48rem) {
padding: 32px;
}
}
嵌套不应该无限加深。推荐控制在较少层级,否则会产生复杂选择器和较高耦合。
17.3 @supports
根据浏览器是否支持某个 CSS 能力启用增强样式:
css
.card-list {
display: flex;
flex-wrap: wrap;
}
@supports (display: grid) {
.card-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
}
检测选择器:
css
@supports selector(:has(*)) {
.card:has(.badge) {
border-color: #2563eb;
}
}
这种做法体现了渐进增强:
- 先提供可用的基础样式
- 浏览器支持新能力时再增强体验
17.4 aspect-ratio
css
.video {
width: 100%;
aspect-ratio: 16 / 9;
}
.avatar {
width: 64px;
aspect-ratio: 1;
border-radius: 50%;
}
17.5 accent-color
css
input[type="checkbox"],
input[type="radio"],
input[type="range"] {
accent-color: #2563eb;
}
17.6 color-scheme
css
:root {
color-scheme: light dark;
}
这会告诉浏览器页面支持的配色方案,使部分表单控件、滚动条和浏览器默认颜色更好地匹配主题。
如果页面没有完整的深色主题,不要仅添加这一行就认为深色模式已经完成。
17.7 @property
@property 可以注册具有类型、初始值和继承行为的自定义属性:
css
@property --progress {
syntax: "<percentage>";
inherits: false;
initial-value: 0%;
}
.progress {
--progress: 0%;
background:
conic-gradient(
#2563eb var(--progress),
#e2e8f0 0
);
transition: --progress 500ms ease;
}
.progress:hover {
--progress: 75%;
}
普通自定义属性通常按字符串标记处理,而注册后的自定义属性可以获得更明确的类型和插值行为。
17.8 content-visibility
对于较长页面中的独立区域:
css
.article-section {
content-visibility: auto;
contain-intrinsic-size: auto 600px;
}
浏览器可以延迟处理屏幕外内容的部分渲染工作。contain-intrinsic-size 可以提供估算尺寸,减少滚动时的布局跳动。
不要盲目应用到所有元素,应通过性能工具确认它是否真正改善了目标页面。
18. CSS 组件设计
18.1 组件基础结构
HTML:
html
<article class="product-card">
<img
class="product-card__image"
src="./images/product.jpg"
alt="机械键盘"
>
<div class="product-card__body">
<h2 class="product-card__title">机械键盘</h2>
<p class="product-card__description">
支持多设备连接和热插拔轴体。
</p>
<button class="button button--primary">查看详情</button>
</div>
</article>
CSS:
css
.product-card {
overflow: hidden;
color: #0f172a;
background: white;
border: 1px solid #e2e8f0;
border-radius: 16px;
box-shadow: 0 10px 30px rgb(15 23 42 / 8%);
}
.product-card__image {
display: block;
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
.product-card__body {
padding: 20px;
}
.product-card__title {
margin: 0 0 8px;
font-size: 1.25rem;
}
.product-card__description {
margin: 0 0 20px;
color: #64748b;
line-height: 1.7;
}
18.2 组件变体
css
.button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 44px;
padding-inline: 18px;
border: 1px solid transparent;
border-radius: 10px;
font: inherit;
font-weight: 600;
cursor: pointer;
}
.button--primary {
color: white;
background: #2563eb;
}
.button--outline {
color: #2563eb;
background: transparent;
border-color: currentColor;
}
.button--danger {
color: white;
background: #dc2626;
}
18.3 用属性表达状态
HTML:
html
<button
class="button"
type="button"
data-variant="primary"
data-loading="false"
>
保存
</button>
CSS:
css
.button[data-variant="primary"] {
color: white;
background: #2563eb;
}
.button[data-loading="true"] {
cursor: wait;
opacity: 0.7;
}
真实交互状态还应同步使用正确的 HTML 属性,例如:
html
<button type="button" disabled aria-busy="true">
正在保存
</button>
不要只依靠自定义属性模拟原生语义。
18.4 组件设计原则
一个可维护的 CSS 组件应该:
- 具有明确边界
- 尽量减少对父级结构的依赖
- 使用低优先级类选择器
- 允许通过变量定制主题
- 明确定义尺寸和状态变体
- 保留键盘焦点样式
- 不依靠固定文案长度
- 能适应不同容器宽度
- 避免内部样式泄漏到其他组件
19. CSS 工程化与代码组织
19.1 推荐目录结构
text
styles/
├── reset.css
├── tokens.css
├── base.css
├── layout.css
├── utilities.css
├── components/
│ ├── button.css
│ ├── card.css
│ ├── modal.css
│ └── form.css
└── pages/
├── home.css
└── profile.css
19.2 设计令牌
css
:root {
/* 颜色 */
--color-primary-500: #3b82f6;
--color-primary-600: #2563eb;
--color-slate-50: #f8fafc;
--color-slate-900: #0f172a;
/* 间距 */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
/* 圆角 */
--radius-sm: 0.375rem;
--radius-md: 0.75rem;
--radius-lg: 1rem;
/* 阴影 */
--shadow-sm: 0 1px 2px rgb(15 23 42 / 6%);
--shadow-md: 0 12px 30px rgb(15 23 42 / 10%);
}
设计令牌可以让颜色、间距和圆角保持统一。
19.3 BEM 命名
BEM 的基本形式:
text
block__element--modifier
示例:
css
.card {
}
.card__title {
}
.card__description {
}
.card--featured {
}
BEM 不是必须采用的标准,但其"组件、子元素、变体"思想有助于减少命名冲突。
19.4 工具类
css
.text-center {
text-align: center;
}
.mt-4 {
margin-top: 1rem;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
}
工具类适合表达单一、稳定的样式职责,但不要同时建立多套相互冲突的命名体系。
19.5 CSS Modules
组件化项目可以使用 CSS Modules:
css
/* Button.module.css */
.button {
padding: 10px 18px;
border-radius: 8px;
}
在 JavaScript 或 TypeScript 中:
js
import styles from "./Button.module.css";
export function Button() {
return <button className={styles.button}>提交</button>;
}
构建工具会把类名转换为局部作用域名称,降低全局命名冲突。
19.6 Sass 等预处理器
Sass 可以提供:
- 变量
- 嵌套
- 混入
- 函数
- 模块化
- 循环和条件
不过现代 CSS 已经原生支持变量、嵌套和大量计算能力。是否引入预处理器,应根据项目实际复杂度决定。
19.7 PostCSS
PostCSS 是处理 CSS 的工具平台,常见用途包括:
- 自动添加部分兼容前缀
- 转换部分新语法
- 压缩 CSS
- 检查代码
- 配合构建工具处理资源
不要手工维护大量浏览器前缀,应根据项目的浏览器支持范围交给工具生成。
19.8 避免过度抽象
下面这种命名过度依赖具体视觉值:
css
.margin-top-17 {
margin-top: 17px;
}
.blue-text-2 {
color: #2878ff;
}
如果项目采用工具类体系,应使用统一的设计尺度;如果采用语义组件体系,应使用组件职责命名。两种方式都比临时数字类名更容易维护。
20. CSS 性能优化
20.1 删除无用 CSS
大型项目经过长期迭代后可能积累大量废弃规则。可以通过以下方式治理:
- 分析构建产物
- 按页面或组件拆分样式
- 删除已下线组件的 CSS
- 配置静态检查
- 检查动态生成类名,避免误删
- 定期审查第三方样式依赖
20.2 减少阻塞资源
CSS 通常会影响首次渲染。可以考虑:
- 压缩生产环境 CSS
- 避免加载不需要的完整组件库样式
- 拆分页面级样式
- 对静态资源启用缓存
- 减少连续的
@import网络依赖 - 避免超大的内联样式
20.3 优化字体
css
@font-face {
font-family: "MyFont";
src: url("./fonts/my-font.woff2") format("woff2");
font-display: swap;
}
建议:
- 优先使用 WOFF2
- 只加载实际需要的字重
- 谨慎加载多套字体
- 合理使用
font-display - 根据项目情况进行字体子集化
20.4 避免布局抖动
图片应提供尺寸或比例:
css
.article-cover {
width: 100%;
aspect-ratio: 16 / 9;
}
HTML 也可以直接声明:
html
<img
src="./cover.jpg"
alt="文章封面"
width="1600"
height="900"
>
这样浏览器在图片加载前就能预留空间。
20.5 减少复杂动画
不要让大量元素持续执行高成本动画:
css
/* 应谨慎评估 */
.large-list-item {
animation: complex-shadow 1s infinite;
}
动画优化原则:
- 只在需要时执行
- 优先使用
transform和opacity - 页面不可见时停止无意义动画
- 尊重
prefers-reduced-motion - 使用性能面板测量,而不是只凭感觉判断
20.6 控制选择器复杂度
不推荐:
css
.page .main .content .article ul li a span {
color: blue;
}
更推荐:
css
.article-link-label {
color: blue;
}
现代浏览器匹配常见选择器通常很快,简化选择器的首要收益是降低耦合和提高可维护性。
21. CSS 与无障碍设计
21.1 保留清晰焦点
css
:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 3px;
}
21.2 保证足够的颜色对比度
不要使用过浅的文字:
css
/* 可能难以阅读 */
.text {
color: #d1d5db;
background: white;
}
正文、按钮、链接、错误信息都需要在实际背景下检查对比度。
21.3 不要只通过颜色表达状态
不完整的错误提示:
css
.input-error {
border-color: red;
}
更好的做法:
html
<label for="email">邮箱</label>
<input
id="email"
type="email"
aria-invalid="true"
aria-describedby="email-error"
>
<p id="email-error" class="error-message">
邮箱格式不正确,请检查后重新输入。
</p>
CSS:
css
.error-message {
display: flex;
gap: 8px;
color: #b91c1c;
font-weight: 600;
}
.error-message::before {
content: "!";
display: grid;
width: 20px;
aspect-ratio: 1;
place-items: center;
border: 2px solid currentColor;
border-radius: 50%;
}
这里同时使用了颜色、图形和文字说明。
21.4 触摸目标尺寸
按钮和可点击区域应具有足够尺寸:
css
.icon-button {
display: inline-grid;
width: 44px;
height: 44px;
place-items: center;
}
21.5 不要通过 CSS 重新排列语义
Grid 和 Flexbox 可以改变视觉顺序,但通常不会同步改变 DOM 阅读顺序和键盘焦点顺序。
HTML 应先按照合理的阅读和操作顺序编写,再使用 CSS 进行视觉布局。
21.6 动画可访问性
css
@media (prefers-reduced-motion: reduce) {
.animated-banner {
animation: none;
transition: none;
}
}
如果动画承载业务含义,还应提供无需动画也能理解信息的方式。
22. 浏览器调试技巧
22.1 检查元素
打开浏览器开发者工具后,可以查看:
- 元素匹配了哪些 CSS 规则
- 哪些属性被覆盖
- 属性来自哪个文件和行号
- 最终计算样式
- 盒模型尺寸
- Flexbox 和 Grid 布局
- 伪类状态
- CSS 变量最终值
22.2 查找样式为什么没有生效
依次检查:
- CSS 文件是否成功加载
- 选择器是否匹配目标元素
- 属性名和值是否合法
- 声明是否被划掉
- 是否被更高层级规则覆盖
- 父元素是否限制了尺寸
- 当前元素是否处于预期布局模式
- 媒体查询或容器查询是否匹配
- 自定义属性是否定义
- 属性是否适用于该元素
22.3 检查盒模型
开发者工具中的盒模型可以查看:
text
content
padding
border
margin
如果尺寸不符合预期,重点检查:
box-sizingwidth和max-widthmin-width- 内外边距
- 边框
- Flex 或 Grid 的尺寸分配
- 文本或图片是否撑开容器
22.4 强制伪类状态
开发者工具通常可以强制:
:hover:active:focus:focus-visible:visited
这对于调试按钮、菜单和表单非常方便。
22.5 临时添加轮廓
css
* {
outline: 1px solid rgb(255 0 0 / 15%);
}
它可以帮助快速观察元素边界,但不要提交到生产环境。
更精确的调试方式:
css
.debug {
outline: 2px solid red;
background: rgb(255 0 0 / 8%);
}
22.6 检查横向滚动
页面出现横向滚动时,可以临时使用:
css
* {
outline: 1px solid red;
}
重点检查:
- 固定宽度元素
width: 100vw- 长文本和长链接
- 绝对定位元素
- 负外边距
- Grid 自动最小尺寸
- 图片缺少
max-width: 100% - Flex 项目缺少
min-width: 0
不要直接用下面的代码掩盖问题:
css
body {
overflow-x: hidden;
}
应该先找到真正产生溢出的元素。
23. 完整响应式页面实战
下面实现一个不依赖 JavaScript 的响应式技术产品首页,综合使用:
- CSS 变量
- 层叠层
- Flexbox
- Grid
- 响应式设计
- 容器查询
- 伪类
- 动画
- 深色模式
- 无障碍焦点
- 减少动画偏好
23.1 项目结构
text
css-demo/
├── index.html
└── style.css
23.2 index.html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
<meta
name="description"
content="一个使用现代 CSS 构建的响应式页面示例"
>
<title>CSS 实战页面</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<a class="skip-link" href="#main-content">
跳转到主要内容
</a>
<header class="site-header">
<div class="container site-header__inner">
<a class="brand" href="#" aria-label="CodeStyle 首页">
<span class="brand__icon" aria-hidden="true">C</span>
<span>CodeStyle</span>
</a>
<nav class="navigation" aria-label="主导航">
<a class="navigation__link navigation__link--active" href="#">
首页
</a>
<a class="navigation__link" href="#features">功能</a>
<a class="navigation__link" href="#stats">数据</a>
<a class="navigation__link" href="#contact">联系</a>
</nav>
<a class="button button--small button--outline" href="#contact">
开始使用
</a>
</div>
</header>
<main id="main-content">
<section class="hero">
<div class="container hero__layout">
<div class="hero__content">
<p class="eyebrow">
现代 CSS 学习示例
</p>
<h1 class="hero__title">
使用 CSS 构建漂亮、快速且响应式的网页
</h1>
<p class="hero__description">
掌握层叠、Flexbox、Grid、容器查询和设计令牌,
建立真正可维护的页面样式体系。
</p>
<div class="hero__actions">
<a class="button button--primary" href="#features">
查看功能
</a>
<a class="button button--ghost" href="#contact">
联系我们
</a>
</div>
<ul class="hero__advantages" aria-label="产品优势">
<li>响应式布局</li>
<li>无障碍设计</li>
<li>现代浏览器能力</li>
</ul>
</div>
<div class="hero__visual" aria-hidden="true">
<div class="code-window">
<div class="code-window__header">
<span class="code-window__dot"></span>
<span class="code-window__dot"></span>
<span class="code-window__dot"></span>
</div>
<pre class="code-window__content"><code><span class="code-selector">.page</span> {
<span class="code-property">display</span>: grid;
<span class="code-property">gap</span>: 2rem;
<span class="code-property">color</span>: <span class="code-value">#2563eb</span>;
}</code></pre>
</div>
</div>
</div>
</section>
<section id="features" class="section feature-section">
<div class="container">
<div class="section-heading">
<p class="eyebrow">核心能力</p>
<h2 class="section-heading__title">
从基础样式到现代布局
</h2>
<p class="section-heading__description">
每一个模块都可以独立复用,并根据容器尺寸自动调整。
</p>
</div>
<div class="feature-grid">
<article class="feature-card">
<span class="feature-card__icon" aria-hidden="true">01</span>
<h3 class="feature-card__title">清晰的层叠关系</h3>
<p class="feature-card__description">
使用层叠层、低优先级选择器和设计令牌,
避免样式覆盖失控。
</p>
<a class="text-link" href="#">了解层叠规则</a>
</article>
<article class="feature-card">
<span class="feature-card__icon" aria-hidden="true">02</span>
<h3 class="feature-card__title">灵活的页面布局</h3>
<p class="feature-card__description">
组合使用 Flexbox 与 Grid,适配从手机到桌面的各种尺寸。
</p>
<a class="text-link" href="#">了解布局方式</a>
</article>
<article class="feature-card">
<span class="feature-card__icon" aria-hidden="true">03</span>
<h3 class="feature-card__title">独立的响应式组件</h3>
<p class="feature-card__description">
使用容器查询,让组件根据实际可用空间改变内部布局。
</p>
<a class="text-link" href="#">了解容器查询</a>
</article>
</div>
</div>
</section>
<section id="stats" class="section section--muted">
<div class="container stats-grid">
<article class="stat-card">
<strong class="stat-card__value">12+</strong>
<span class="stat-card__label">核心章节</span>
</article>
<article class="stat-card">
<strong class="stat-card__value">30+</strong>
<span class="stat-card__label">代码示例</span>
</article>
<article class="stat-card">
<strong class="stat-card__value">100%</strong>
<span class="stat-card__label">响应式布局</span>
</article>
<article class="stat-card">
<strong class="stat-card__value">0</strong>
<span class="stat-card__label">JavaScript 依赖</span>
</article>
</div>
</section>
<section id="contact" class="section">
<div class="container">
<div class="contact-card">
<div>
<p class="eyebrow">开始实践</p>
<h2 class="contact-card__title">
从一个小组件开始学习 CSS
</h2>
<p class="contact-card__description">
先实现结构,再完成基础样式,最后逐步增加响应式和动画。
</p>
</div>
<a class="button button--primary" href="mailto:hello@example.com">
立即联系
</a>
</div>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container site-footer__inner">
<p>© 2026 CodeStyle. CSS 学习示例。</p>
<a href="#">返回顶部</a>
</div>
</footer>
</body>
</html>
23.3 style.css
css
@layer reset, tokens, base, layout, components, utilities;
@layer reset {
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body,
h1,
h2,
h3,
p,
ul {
margin: 0;
}
ul {
padding: 0;
}
img,
svg {
display: block;
max-width: 100%;
}
button,
input,
textarea,
select {
font: inherit;
}
}
@layer tokens {
:root {
color-scheme: light;
--color-primary: #2563eb;
--color-primary-dark: #1d4ed8;
--color-secondary: #7c3aed;
--color-text: #0f172a;
--color-text-muted: #64748b;
--color-page: #ffffff;
--color-surface: #ffffff;
--color-surface-muted: #f8fafc;
--color-border: #e2e8f0;
--container-width: 72rem;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-5: 1.25rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-10: 2.5rem;
--space-12: 3rem;
--space-16: 4rem;
--space-20: 5rem;
--radius-sm: 0.5rem;
--radius-md: 0.75rem;
--radius-lg: 1rem;
--radius-xl: 1.5rem;
--shadow-sm: 0 1px 3px rgb(15 23 42 / 8%);
--shadow-md: 0 16px 40px rgb(15 23 42 / 10%);
}
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
--color-text: #e2e8f0;
--color-text-muted: #94a3b8;
--color-page: #020617;
--color-surface: #0f172a;
--color-surface-muted: #111c30;
--color-border: #263449;
--shadow-sm: 0 1px 3px rgb(0 0 0 / 30%);
--shadow-md: 0 16px 40px rgb(0 0 0 / 35%);
}
}
}
@layer base {
body {
min-width: 20rem;
color: var(--color-text);
background: var(--color-page);
font-family:
Inter,
"PingFang SC",
"Microsoft YaHei",
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
line-height: 1.6;
}
a {
color: inherit;
text-decoration: none;
}
:focus-visible {
outline: 3px solid rgb(37 99 235 / 40%);
outline-offset: 3px;
}
}
@layer layout {
.container {
width: min(100% - 2rem, var(--container-width));
margin-inline: auto;
}
.section {
padding-block: clamp(4rem, 8vw, 7rem);
}
.section--muted {
background: var(--color-surface-muted);
}
.site-header__inner,
.site-footer__inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-6);
}
}
@layer components {
.skip-link {
position: fixed;
z-index: 1000;
top: var(--space-4);
left: var(--space-4);
padding: var(--space-3) var(--space-4);
color: white;
background: var(--color-primary);
border-radius: var(--radius-sm);
transform: translateY(-200%);
}
.skip-link:focus {
transform: translateY(0);
}
.site-header {
position: sticky;
z-index: 20;
top: 0;
border-bottom: 1px solid var(--color-border);
background: color-mix(
in srgb,
var(--color-page) 85%,
transparent
);
backdrop-filter: blur(14px);
}
.site-header__inner {
min-height: 4.5rem;
}
.brand {
display: inline-flex;
align-items: center;
gap: var(--space-3);
font-size: 1.125rem;
font-weight: 800;
}
.brand__icon {
display: grid;
width: 2.25rem;
aspect-ratio: 1;
place-items: center;
color: white;
background:
linear-gradient(
135deg,
var(--color-primary),
var(--color-secondary)
);
border-radius: var(--radius-sm);
}
.navigation {
display: none;
align-items: center;
gap: var(--space-2);
}
.navigation__link {
padding: var(--space-2) var(--space-3);
color: var(--color-text-muted);
border-radius: var(--radius-sm);
transition:
color 160ms ease,
background-color 160ms ease;
}
.navigation__link:hover,
.navigation__link--active {
color: var(--color-primary);
background: rgb(37 99 235 / 8%);
}
.button {
display: inline-flex;
min-height: 2.75rem;
align-items: center;
justify-content: center;
padding-inline: 1.25rem;
border: 1px solid transparent;
border-radius: var(--radius-md);
font-weight: 700;
text-align: center;
cursor: pointer;
transition:
color 160ms ease,
background-color 160ms ease,
border-color 160ms ease,
transform 160ms ease;
}
.button:hover {
transform: translateY(-2px);
}
.button--small {
min-height: 2.5rem;
padding-inline: 1rem;
font-size: 0.875rem;
}
.button--primary {
color: white;
background: var(--color-primary);
box-shadow: 0 10px 24px rgb(37 99 235 / 25%);
}
.button--primary:hover {
background: var(--color-primary-dark);
}
.button--outline {
color: var(--color-primary);
border-color: currentColor;
}
.button--ghost {
color: var(--color-text);
background: var(--color-surface);
border-color: var(--color-border);
}
.hero {
position: relative;
overflow: hidden;
padding-block: clamp(4rem, 10vw, 8rem);
background:
radial-gradient(
circle at 15% 10%,
rgb(37 99 235 / 14%),
transparent 32%
),
radial-gradient(
circle at 90% 80%,
rgb(124 58 237 / 14%),
transparent 30%
);
}
.hero__layout {
display: grid;
align-items: center;
gap: var(--space-12);
}
.hero__content {
max-width: 42rem;
}
.eyebrow {
margin-bottom: var(--space-4);
color: var(--color-primary);
font-size: 0.8125rem;
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.hero__title {
max-width: 15ch;
font-size: clamp(2.5rem, 7vw, 5rem);
line-height: 1.08;
letter-spacing: -0.04em;
}
.hero__description {
max-width: 60ch;
margin-top: var(--space-6);
color: var(--color-text-muted);
font-size: clamp(1rem, 2vw, 1.25rem);
}
.hero__actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-4);
margin-top: var(--space-8);
}
.hero__advantages {
display: flex;
flex-wrap: wrap;
gap: var(--space-4) var(--space-6);
margin-top: var(--space-8);
color: var(--color-text-muted);
list-style: none;
}
.hero__advantages li {
display: flex;
align-items: center;
gap: var(--space-2);
}
.hero__advantages li::before {
content: "✓";
display: grid;
width: 1.25rem;
aspect-ratio: 1;
place-items: center;
color: white;
background: #16a34a;
border-radius: 50%;
font-size: 0.75rem;
font-weight: 900;
}
.hero__visual {
min-width: 0;
}
.code-window {
overflow: hidden;
max-width: 34rem;
margin-inline: auto;
background: #0f172a;
border: 1px solid rgb(255 255 255 / 12%);
border-radius: var(--radius-xl);
box-shadow: 0 30px 80px rgb(15 23 42 / 28%);
transform: rotate(2deg);
}
.code-window__header {
display: flex;
gap: var(--space-2);
padding: var(--space-4);
border-bottom: 1px solid rgb(255 255 255 / 10%);
}
.code-window__dot {
width: 0.75rem;
aspect-ratio: 1;
background: #fb7185;
border-radius: 50%;
}
.code-window__dot:nth-child(2) {
background: #fbbf24;
}
.code-window__dot:nth-child(3) {
background: #4ade80;
}
.code-window__content {
overflow: auto;
margin: 0;
padding: clamp(1.5rem, 5vw, 3rem);
color: #e2e8f0;
font: 600 clamp(0.9rem, 2vw, 1.15rem) / 1.9
ui-monospace,
SFMono-Regular,
Menlo,
Consolas,
monospace;
}
.code-selector {
color: #c084fc;
}
.code-property {
color: #7dd3fc;
}
.code-value {
color: #86efac;
}
.section-heading {
max-width: 42rem;
margin-bottom: var(--space-12);
}
.section-heading__title {
font-size: clamp(2rem, 5vw, 3.25rem);
line-height: 1.15;
letter-spacing: -0.03em;
}
.section-heading__description {
margin-top: var(--space-4);
color: var(--color-text-muted);
font-size: 1.125rem;
}
.feature-section {
container: features / inline-size;
}
.feature-grid {
display: grid;
gap: var(--space-6);
}
.feature-card {
display: grid;
grid-template-rows: auto auto 1fr auto;
gap: var(--space-4);
padding: clamp(1.5rem, 4cqi, 2.25rem);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-sm);
transition:
border-color 180ms ease,
box-shadow 180ms ease,
transform 180ms ease;
}
.feature-card:hover {
border-color: rgb(37 99 235 / 45%);
box-shadow: var(--shadow-md);
transform: translateY(-4px);
}
.feature-card__icon {
display: grid;
width: 3rem;
aspect-ratio: 1;
place-items: center;
color: var(--color-primary);
background: rgb(37 99 235 / 10%);
border-radius: var(--radius-md);
font-weight: 900;
}
.feature-card__title {
font-size: 1.25rem;
}
.feature-card__description {
color: var(--color-text-muted);
}
.text-link {
width: fit-content;
color: var(--color-primary);
font-weight: 700;
}
.text-link::after {
content: " →";
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--space-4);
}
.stat-card {
display: grid;
gap: var(--space-2);
padding: var(--space-6);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
}
.stat-card__value {
color: var(--color-primary);
font-size: clamp(2rem, 5vw, 3.5rem);
line-height: 1;
}
.stat-card__label {
color: var(--color-text-muted);
}
.contact-card {
display: grid;
align-items: center;
gap: var(--space-8);
padding: clamp(2rem, 6vw, 4rem);
color: white;
background:
linear-gradient(
135deg,
var(--color-primary),
var(--color-secondary)
);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-md);
}
.contact-card .eyebrow {
color: rgb(255 255 255 / 75%);
}
.contact-card__title {
max-width: 20ch;
font-size: clamp(1.75rem, 4vw, 3rem);
line-height: 1.15;
}
.contact-card__description {
max-width: 55ch;
margin-top: var(--space-4);
color: rgb(255 255 255 / 82%);
}
.contact-card .button--primary {
color: var(--color-primary);
background: white;
box-shadow: none;
}
.site-footer {
padding-block: var(--space-8);
color: var(--color-text-muted);
border-top: 1px solid var(--color-border);
}
.site-footer__inner {
flex-direction: column;
text-align: center;
}
.site-footer a {
color: var(--color-primary);
font-weight: 700;
}
@container features (min-width: 42rem) {
.feature-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 48rem) {
.navigation {
display: flex;
}
.stats-grid {
grid-template-columns: repeat(4, 1fr);
}
.contact-card {
grid-template-columns: minmax(0, 1fr) auto;
}
.site-footer__inner {
flex-direction: row;
text-align: start;
}
}
@media (min-width: 64rem) {
.hero__layout {
grid-template-columns: minmax(0, 1.1fr) minmax(24rem, 0.9fr);
}
}
}
@layer utilities {
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip-path: inset(50%);
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
23.4 实战项目知识点
这个页面包含以下设计思想:
- 通过
@layer管理样式覆盖顺序 - 通过 CSS 变量统一颜色、间距、圆角和阴影
- 使用移动优先的媒体查询
- 使用 Grid 完成主要二维布局
- 使用 Flexbox 完成导航和按钮组布局
- 使用容器查询控制卡片布局
- 使用
clamp()实现流式字号 - 使用逻辑属性提高国际化适配能力
- 使用
prefers-color-scheme提供深色模式 - 使用
prefers-reduced-motion尊重用户动画偏好 - 使用
:focus-visible保留键盘焦点 - 使用跳转链接帮助键盘用户快速进入主要内容
24. 常见问题与面试知识
24.1 为什么 height: 100% 没有效果
百分比高度通常需要父元素具有可确定的高度:
css
html,
body {
height: 100%;
}
.page {
min-height: 100%;
}
如果目标是页面至少占满视口,可以使用:
css
.page {
min-height: 100dvh;
}
24.2 为什么文字省略号没有出现
需要满足:
css
.text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
并且元素需要受到宽度约束。
在 Flexbox 中可能还需要:
css
.flex-item {
min-width: 0;
}
24.3 为什么 z-index 没有效果
检查:
- 元素是否位于不同层叠上下文
- 祖先是否设置了
transform - 祖先是否设置了
opacity - 是否存在
isolation - 元素的定位方式
- 比较的是不是同一个层叠上下文内的元素
24.4 为什么 position: sticky 失效
常见原因:
- 没有设置
top或其他边界 - 父容器没有足够滚动距离
- 祖先的
overflow创建了不同滚动容器 - 元素高度过大
- 实际滚动的不是预期容器
24.5 display: none、visibility: hidden 和 opacity: 0 的区别
| 属性 | 是否占据空间 | 是否可见 | 是否可能继续交互 |
|---|---|---|---|
display: none |
否 | 否 | 通常否 |
visibility: hidden |
是 | 否 | 通常否 |
opacity: 0 |
是 | 否 | 是 |
24.6 em 和 rem 的区别
rem相对于根元素字体大小em根据使用属性的上下文,通常与当前元素字体大小有关- 在
font-size上使用em时,会根据父元素的字体大小计算 - 多层嵌套使用
em可能出现累积放大
24.7 Flexbox 和 Grid 的区别
- Flexbox 主要处理一维排列
- Grid 主要处理二维网格
- Flexbox 更关注内容分配
- Grid 更关注行列轨道
- 实际项目中可以组合使用
24.8 什么是 BFC
BFC 可以理解为块级格式化上下文,是一个相对独立的布局区域。
常见创建方式包括:
css
.element {
display: flow-root;
}
其他布局或特定属性也可能创建 BFC,例如:
display: flexdisplay: grid- 绝对定位
- 浮动
- 某些非
visible的overflow
display: flow-root 是为了创建独立块级格式化上下文时较清晰的写法:
css
.container {
display: flow-root;
}
24.9 如何清除浮动
旧式方法:
css
.clearfix::after {
content: "";
display: table;
clear: both;
}
现代写法:
css
.container {
display: flow-root;
}
新项目的主要布局通常应该优先考虑 Flexbox 或 Grid,而不是依靠浮动。
24.10 如何实现水平垂直居中
Flexbox:
css
.parent {
display: flex;
align-items: center;
justify-content: center;
}
Grid:
css
.parent {
display: grid;
place-items: center;
}
绝对定位:
css
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
优先选择符合整体布局需求的方式。
24.11 什么是重排和重绘
可以简化理解为:
- 重排:布局信息发生变化,需要重新计算元素几何位置和尺寸
- 重绘:视觉外观变化,需要重新绘制像素
- 合成:浏览器把不同图层组合到最终画面
具体渲染过程由浏览器实现决定。性能优化时应使用开发者工具测量,不要只依靠固定规则猜测。
24.12 为什么不建议滥用 !important
因为它会:
- 打乱正常层叠
- 增加后续覆盖难度
- 形成更多
!important - 让组件难以复用
- 隐藏选择器设计问题
适合使用的场景通常非常有限,例如必须覆盖不可控第三方行内样式的特定补丁,或者明确设计为高优先级的少量工具规则。
24.13 常见 CSS 错误
错误一:固定宽度导致移动端溢出
css
.container {
width: 1200px;
}
改进:
css
.container {
width: min(100% - 32px, 1200px);
margin-inline: auto;
}
错误二:过深选择器
css
.page .main .article .header .title {
color: blue;
}
改进:
css
.article-title {
color: blue;
}
错误三:使用定位完成普通布局
css
.card {
position: absolute;
left: 320px;
top: 180px;
}
普通页面布局应优先使用 Grid、Flexbox 和文档流。
错误四:删除焦点样式
css
button:focus {
outline: none;
}
改进:
css
button:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 3px;
}
错误五:动画所有属性
css
.card {
transition: all 0.5s;
}
改进:
css
.card {
transition:
transform 180ms ease,
box-shadow 180ms ease;
}
25. CSS 学习路线
25.1 第一阶段:基础语法
需要掌握:
- CSS 引入方式
- 选择器
- 属性和值
- 颜色和单位
- 文本和字体
- 背景和边框
练习目标:
- 完成个人简介页
- 完成文章详情页
- 完成登录表单
25.2 第二阶段:盒模型与文档流
需要掌握:
- 盒模型
box-sizing- 内外边距
- 宽高约束
display- 文档流
- 定位
- 层叠上下文
练习目标:
- 完成卡片组件
- 完成固定导航栏
- 完成带角标的商品卡片
25.3 第三阶段:现代布局
需要掌握:
- Flexbox
- Grid
gapminmax()repeat()auto-fit- 对齐方式
练习目标:
- 完成导航栏
- 完成后台管理布局
- 完成响应式卡片网格
- 完成图片画廊
25.4 第四阶段:响应式设计
需要掌握:
- 媒体查询
- 移动优先
- 相对单位
clamp()- 响应式图片
- 逻辑属性
- 容器查询
练习目标:
- 将桌面页面适配到手机
- 完成响应式博客首页
- 完成可在侧边栏复用的卡片组件
25.5 第五阶段:交互与动画
需要掌握:
- 伪类
- 伪元素
transformtransitionanimationprefers-reduced-motion
练习目标:
- 完成按钮状态
- 完成下拉菜单
- 完成加载动画
- 完成卡片悬浮效果
25.6 第六阶段:工程化
需要掌握:
- CSS 变量
- 设计令牌
- 组件命名
- 层叠层
- CSS Modules
- Sass
- PostCSS
- 构建产物分析
- 浏览器兼容策略
练习目标:
- 建立一套按钮组件
- 建立主题变量
- 建立统一间距和颜色系统
- 重构一个存在样式冲突的旧页面
25.7 第七阶段:性能和无障碍
需要掌握:
- 焦点样式
- 颜色对比度
- 减少动画偏好
- 字体加载
- 布局稳定性
- CSS 体积治理
- 浏览器性能工具
练习目标:
- 使用键盘完整操作页面
- 检查深色模式
- 检查页面横向溢出
- 检查图片加载前后的布局变化
- 分析并删除无用 CSS
26. 总结
CSS 的学习不应该停留在背诵属性。真正重要的是理解它背后的规则:
- 选择器决定样式匹配哪些元素
- 层叠决定冲突时哪个声明胜出
- 继承让部分样式沿元素树传播
- 盒模型决定元素的实际尺寸
- 文档流决定元素的基础排列方式
- Flexbox 适合一维布局
- Grid 适合二维布局
- 媒体查询适合页面级响应式设计
- 容器查询适合组件级响应式设计
- CSS 变量适合建立主题和设计令牌
- 层叠层可以降低大型项目的覆盖复杂度
- 性能、无障碍和可维护性同样属于 CSS 的核心能力
学习 CSS 最有效的方法是:
text
理解规则 → 编写页面 → 使用开发者工具检查 → 找到原因 → 重构代码
当你能够解释"为什么这个样式会生效""为什么元素出现在这里""为什么页面发生溢出"时,就已经从记忆属性进入了真正掌握 CSS 的阶段。