引言:
CSS 编程是使用 CSS 变量、数学函数、条件逻辑、模块化作用域等特性构建复杂样式系统的工程化方法论。本文完整系统地讲解 CSS 自定义属性、calc/min/clamp 等运算函数、@media/@supports/@container 条件查询、@layer/@scope 模块化机制、设计系统与主题构建、CSS 与 JavaScript 协同,以及嵌套、:has()、视图过渡等最新演进特性,涵盖 CSS 从基础到工程化的完整知识体系。

CSS 编程:从样式表到真正的“工程化”

当我们谈论“CSS 编程”时,我们并不是在说 CSS 是一门像 JavaScript 那样的通用编程语言。CSS 本质上是一种声明式语言——你告诉浏览器“元素应该长什么样”,而不是“如何一步步计算出来”。

但是,随着 Web 应用变得越来越复杂,CSS 也在不断进化,获得了很多传统意义上属于“编程语言”的特性:变量、逻辑判断、数学计算、函数、作用域、模块化……这些能力让 CSS 从简单的“样式描述”变成了可以构建复杂设计系统的“工程化工具”。

本文将完整、系统地讲解 CSS 编程的方方面面,涵盖 CSS 变量、运算与函数、条件逻辑、模块化作用域、设计系统、主题切换、CSS 与 JavaScript 协同,以及 CSS 的最新演进特性。

引言:CSS 是编程语言吗?

这个问题在开发者社区一直有争议。我们可以从几个维度来看:

支持“CSS 是编程语言”的观点:

  • 它有语法规则语义
  • 它有计算能力(通过 calc()min()max() 等函数)。
  • 它有变量(CSS 自定义属性)。
  • 它有条件逻辑(通过 @media@supports 查询)。
  • 它有函数(颜色函数、数学函数、自定义函数等)。

反对观点:

  • 没有循环迭代结构。
  • 无法定义算法程序流程
  • 不能处理输入/输出(除了修改样式)。
  • 缺少抽象数据类型

结论:CSS 不是一种通用编程语言,但它是一种特定领域的编程语言(DSL,Domain Specific Language)。它的“编程”体现在:用声明式的方式描述视觉逻辑,用工程化的手段管理复杂样式系统。

第一章:CSS 变量——样式表的“存储引擎”

变量是任何编程语言的基石。CSS 自定义属性(CSS Variables)让样式表拥有了真正的“存储与复用”能力。

1.1 变量的声明与使用

声明一个自定义属性,需要使用以两个连字符 -- 开头的名称,并像普通属性一样赋予它一个值。

1
2
3
4
5
6
7
8
9
10
/* 在 :root 中声明全局变量 */
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--spacing-unit: 8px;
--border-radius: 4px;
--font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--transition-duration: 0.3s;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

使用变量时,通过 var() 函数来引用它。var() 函数接受一个变量名作为必要参数,并可以接受一个可选的“回退值”,当变量未定义时使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.button {
background-color: var(--primary-color);
padding: calc(var(--spacing-unit) * 2) calc(var(--spacing-unit) * 4);
border-radius: var(--border-radius);
font-family: var(--font-family);
transition: all var(--transition-duration) ease;
box-shadow: var(--shadow);
}

/* 带回退值的用法 */
.button-secondary {
background-color: var(--secondary-color, #2ecc71);
/* 如果 --secondary-color 未定义,则使用 #2ecc71 作为回退 */
}

1.2 变量的作用域与继承

自定义属性遵循 CSS 的层叠和继承规则。这意味着你可以为不同的元素或类分别定义相同的变量名,从而实现局部覆盖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* 全局作用域 */
:root {
--bg-color: #ffffff;
--text-color: #333333;
--card-bg: #f5f5f5;
}

/* 深色主题容器内覆盖变量 */
.dark-theme {
--bg-color: #1a1a1a;
--text-color: #f0f0f0;
--card-bg: #2d2d2d;
}

/* 卡片组件内再次覆盖 */
.card {
--card-bg: #ffffff;
background: var(--card-bg);
color: var(--text-color);
}

/* 深色主题下的卡片 */
.dark-theme .card {
--card-bg: #2d2d2d;
/* 继承自 .dark-theme 的 --text-color */
}

body {
background: var(--bg-color);
color: var(--text-color);
}

1.3 JavaScript 操作 CSS 变量

CSS 变量是 CSS 和 JavaScript 之间的“桥梁”——JavaScript 可以读写 CSS 变量,实现动态样式控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 读取 CSS 变量
const root = document.documentElement;
const primaryColor = getComputedStyle(root)
.getPropertyValue('--primary-color').trim();
console.log(primaryColor); // "#3498db"

// 修改 CSS 变量(动态主题切换)
function setTheme(color) {
document.documentElement.style.setProperty('--primary-color', color);
}

// 根据用户输入动态调整
const colorPicker = document.querySelector('#color-picker');
colorPicker.addEventListener('input', (e) => {
document.documentElement.style.setProperty('--primary-color', e.target.value);
});

// 批量修改变量
function applyTheme(theme) {
const root = document.documentElement;
Object.entries(theme).forEach(([key, value]) => {
root.style.setProperty(`--${key}`, value);
});
}

// 使用示例
const darkTheme = {
'bg-color': '#1a1a1a',
'text-color': '#f0f0f0',
'card-bg': '#2d2d2d'
};
applyTheme(darkTheme);

1.4 CSS 变量的高级用法

在 CSS 变量中存储复杂值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
:root {
/* 存储颜色组合 */
--gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--gradient-secondary: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);

/* 存储滤镜 */
--blur-filter: blur(8px);
--brightness-filter: brightness(1.2);

/* 存储变换 */
--card-transform: rotate(2deg) scale(1.02);
}

.hero {
background: var(--gradient-primary);
}

.modal-backdrop {
filter: var(--blur-filter);
}

.card:hover {
transform: var(--card-transform);
}

变量与 calc() 结合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
:root {
--header-height: 60px;
--footer-height: 80px;
--content-min-height: calc(100vh - var(--header-height) - var(--footer-height));
}

.main-content {
min-height: var(--content-min-height);
}

/* 响应式变量 */
:root {
--container-padding: 16px;
}

@media (min-width: 768px) {
:root {
--container-padding: 32px;
}
}

@media (min-width: 1200px) {
:root {
--container-padding: 48px;
}
}

.container {
padding: 0 var(--container-padding);
}

第二章:CSS 运算与函数

CSS 提供了多种内置函数和运算能力,可以进行数值计算和颜色处理。

2.1 数学运算函数

calc():四则运算

calc() 允许你在 CSS 属性值中进行加、减、乘、除运算,可以混合使用不同单位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* 基础运算 */
.container {
width: calc(100% - 40px);
/* 百分比与像素混合计算 */
margin: calc(10px + 2vw);
/* 相对单位与绝对单位混合 */
font-size: calc(1rem + 0.5vw);
/* 复杂表达式 */
padding: calc((var(--spacing) * 2) + 4px);
}

/* 居中定位 */
.centered {
position: absolute;
top: calc(50% - 50px);
left: calc(50% - 50px);
width: 100px;
height: 100px;
}

/* 响应式间距 */
.grid {
gap: calc(8px + (24 - 8) * ((100vw - 320px) / (1200 - 320)));
/* 从 8px 到 24px 随视口宽度变化 */
}

min():取最小值

min() 函数从多个值中取最小值,非常适合设置响应式的最大尺寸。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 宽度不超过 1200px,但不会小于 300px */
.container {
width: min(1200px, 90%);
/* 在宽屏上为 1200px,在小屏上为视口的 90% */
}

/* 字体大小取较小的那个值 */
.heading {
font-size: min(3rem, 6vw);
/* 在移动设备上不会太大,在桌面上不会太小 */
}

/* 间距取较小值 */
.sidebar {
padding: min(2rem, 4%);
}

max():取最大值

max() 函数从多个值中取最大值,适合设置响应式的最小尺寸。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 宽度至少为 300px,但不超过 100% */
.container {
width: max(300px, 70%);
}

/* 字体至少为 1rem */
.text {
font-size: max(1rem, 1.2vw);
}

/* 间距至少为 8px */
.element {
padding: max(8px, 2%);
}

clamp():在最小值和最大值之间取值

clamp()min()max() 的结合,接受三个参数:最小值、首选值、最大值。它是响应式设计的利器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 响应式字体:最小 1rem,理想值 2vw + 1rem,最大 2rem */
h1 {
font-size: clamp(1.5rem, 4vw, 3rem);
/* 在 375px 视口下约为 1.5rem,在 1200px 视口下约为 3rem */
}

/* 响应式容器宽度 */
.container {
width: clamp(320px, 80%, 1200px);
/* 最小 320px,最大 1200px,理想为 80% */
}

/* 响应式间距 */
.element {
padding: clamp(0.5rem, 2vw, 2rem);
}

/* 响应式网格列数 */
.grid {
grid-template-columns: repeat(auto-fit, minmax(clamp(150px, 25%, 300px), 1fr));
}

2.2 颜色函数

基础颜色函数:rgb()rgba()hsl()hsla()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* RGB 颜色 */
.element {
color: rgb(52, 152, 219);
background: rgba(52, 152, 219, 0.5);
}

/* HSL 颜色(色相、饱和度、亮度) */
.element {
color: hsl(210, 80%, 50%);
background: hsla(210, 80%, 50%, 0.5);
}

/* 使用 CSS 变量动态控制颜色 */
:root {
--brand-hue: 210;
--brand-saturation: 80%;
--brand-lightness: 50%;
}

.button {
background: hsl(var(--brand-hue), var(--brand-saturation), var(--brand-lightness));
}

.button:hover {
background: hsl(var(--brand-hue), var(--brand-saturation), calc(var(--brand-lightness) - 10%));
}

color-mix():混合颜色(CSS Color Level 5)

color-mix() 允许在颜色空间中混合两种颜色,指定混合比例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* 基础混合 */
.button-primary {
background: color-mix(in srgb, #3498db 70%, white);
/* 70% #3498db + 30% white */
}

.button-primary-hover {
background: color-mix(in srgb, #3498db 70%, black);
/* 70% #3498db + 30% black,得到更暗的版本 */
}

/* 使用 CSS 变量 */
:root {
--primary: #3498db;
}
.button {
background: color-mix(in srgb, var(--primary) 80%, var(--secondary) 20%);
}

/* 不同颜色空间的混合 */
.element {
/* 在 HSL 颜色空间中混合 */
background: color-mix(in hsl, red 50%, blue);
/* 在 OKLCH 颜色空间中混合(更精确) */
border-color: color-mix(in oklch, #3498db 60%, #2ecc71);
}

color-contrast():选择最佳对比色

color-contrast() 函数从多个候选颜色中选出与基准颜色对比度最高的那个,用于确保可访问性。

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 自动选择与背景对比度最高的文字颜色 */
.element {
background: #3498db;
color: color-contrast(#3498db vs white, black);
/* 如果 #3498db 与 white 的对比度足够高,使用 white;否则使用 black */
}

/* 多个候选颜色 */
.element {
background: #1a1a1a;
color: color-contrast(#1a1a1a vs white, #f0f0f0, #cccccc);
/* 选择与 #1a1a1a 对比度最高的颜色 */
}

其他颜色函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 调整颜色的亮度 */
.element {
background: hsl(from var(--primary) h s calc(l + 10%));
}

/* 相对颜色语法(从现有颜色创建新颜色) */
:root {
--primary: #3498db;
--primary-light: hsl(from var(--primary) h s calc(l + 20%));
--primary-dark: hsl(from var(--primary) h s calc(l - 20%));
}

/* 颜色解析函数(将颜色字符串转换为 RGB 分量) */
/* 这通常需要 JavaScript 辅助,CSS 原生支持有限 */

2.3 其他常用函数

attr():获取 HTML 属性值

attr() 函数允许从 HTML 元素的属性中获取值并用于样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 基础用法:从 data 属性获取值 */
.element {
content: attr(data-label);
background-color: attr(data-color, #3498db);
/* 如果 data-color 不存在,使用回退值 #3498db */
}

/* 结合 CSS 变量使用 */
.tooltip::after {
content: attr(data-tooltip);
position: absolute;
background: #333;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
1
2
3
4
<!-- HTML 中的使用 -->
<div class="element" data-label="提示信息" data-color="#e74c3c">
悬停查看提示
</div>

url():加载外部资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 加载图片 */
.hero {
background-image: url('/images/hero.jpg');
}

/* 动态图片(结合 CSS 变量) */
:root {
--hero-image: url('/images/hero-default.jpg');
}

.hero {
background-image: var(--hero-image);
}

/* 使用媒体查询切换图片 */
@media (min-resolution: 2dppx) {
.hero {
background-image: url('/images/hero@2x.jpg');
}
}

var() 与自定义属性的组合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 复杂的变量组合 */
:root {
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;

--spacing: var(--spacing-md); /* 默认间距 */
}

.card {
padding: var(--spacing);
margin-bottom: var(--spacing-lg);
}

/* 在媒体查询中覆盖变量 */
@media (max-width: 768px) {
:root {
--spacing: var(--spacing-sm);
--spacing-lg: var(--spacing-md);
}
}

第三章:CSS 条件逻辑

CSS 中的条件逻辑主要通过 @media 查询@supports 查询@container 查询 实现。

3.1 @media 媒体查询

媒体查询是 CSS 最基础的条件逻辑,根据设备特性应用不同的样式。

视口相关查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* 根据视口宽度调整布局 */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}

/* 平板设备 */
@media (max-width: 1024px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}

/* 手机设备 */
@media (max-width: 768px) {
.grid {
grid-template-columns: 1fr;
}
}

/* 组合条件:宽度在 768px 到 1024px 之间 */
@media (min-width: 768px) and (max-width: 1024px) {
.sidebar {
display: none;
}
}

/* 高度相关的查询 */
@media (min-height: 800px) {
.hero {
min-height: 80vh;
}
}

设备特性查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/* 暗色模式偏好 */
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a1a;
--text: #f0f0f0;
--card-bg: #2d2d2d;
--border-color: #404040;
}
}

@media (prefers-color-scheme: light) {
:root {
--bg: #ffffff;
--text: #222222;
--card-bg: #f5f5f5;
--border-color: #e0e0e0;
}
}

/* 减少动画偏好(为运动敏感用户设计) */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

/* 高对比度偏好 */
@media (prefers-contrast: high) {
:root {
--text: #000000;
--bg: #ffffff;
--border-color: #000000;
--shadow: none;
}
}

/* 系统主题色偏好 */
@media (prefers-color-scheme: dark) {
.theme-toggle-icon {
content: "🌙";
}
}

@media (prefers-color-scheme: light) {
.theme-toggle-icon {
content: "☀️";
}
}

设备类型查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* 打印样式 */
@media print {
.no-print {
display: none !important;
}
body {
background: white;
color: black;
font-size: 12pt;
}
a[href]::after {
content: " (" attr(href) ")";
}
}

/* 屏幕阅读器专用样式 */
@media speech {
.sr-only {
display: block;
}
}

/* 触摸设备 */
@media (hover: none) and (pointer: coarse) {
/* 触摸设备上的样式调整 */
.button {
min-height: 44px;
min-width: 44px;
padding: 12px 16px;
}
}

3.2 @supports 特性检测

@supports 查询允许检测浏览器是否支持某个 CSS 特性,从而提供回退方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/* 检测 Grid 布局支持 */
@supports (display: grid) {
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
}
}

@supports not (display: grid) {
.container {
display: flex;
flex-wrap: wrap;
}
.container > * {
flex: 1 1 33.333%;
margin: 10px;
}
}

/* 检测嵌套语法支持 */
@supports (selector(&)) {
.card {
&__title {
font-size: 1.5rem;
}
&__body {
padding: 1rem;
}
}
}

/* 检测容器查询支持 */
@supports (container-type: inline-size) {
.card-container {
container-type: inline-size;
}

@container (min-width: 400px) {
.card {
display: flex;
flex-direction: row;
}
}
}

/* 检测子网格支持 */
@supports (grid-template-columns: subgrid) {
.grid {
display: grid;
grid-template-columns: subgrid;
}
}

/* 检测 has() 选择器支持 */
@supports (selector(:has(*))) {
.container:has(.featured) {
background: var(--highlight);
}
}

/* 组合检测 */
@supports (display: grid) and (gap: 20px) {
.grid {
display: grid;
gap: 20px;
}
}

3.3 @container 容器查询

容器查询是 CSS 响应式设计的重大突破——它允许组件根据其容器的大小来调整样式,而不是视口大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/* 定义容器 */
.card-container {
container-type: inline-size;
container-name: card;
}

/* 或者简写 */
.card-wrapper {
container: card / inline-size;
}

/* 根据容器宽度调整样式 */
@container card (min-width: 400px) {
.card {
display: flex;
flex-direction: row;
align-items: center;
}

.card-image {
flex: 0 0 40%;
margin-right: 20px;
}
}

@container card (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}

.card-image {
margin-bottom: 16px;
}
}

/* 容器高度查询 */
@container card (min-height: 300px) {
.card-content {
display: flex;
flex-direction: column;
justify-content: center;
}
}

/* 容器尺寸区间查询 */
@container card (min-width: 400px) and (max-width: 800px) {
.card {
padding: 16px;
}
}

/* 使用容器查询的响应式组件示例 */
.product-card {
container: product / inline-size;
}

@container product (min-width: 300px) {
.product-card {
display: grid;
grid-template-columns: 120px 1fr;
gap: 16px;
}
}

@container product (min-width: 500px) {
.product-card {
grid-template-columns: 200px 1fr;
gap: 24px;
}

.product-title {
font-size: 1.5rem;
}
}

第四章:CSS 模块化与作用域

4.1 @layer 级联层

@layer 是 CSS 的一个相对较新的特性,用于控制样式规则在不同层级之间的优先级,而不需要依赖选择器权重或 !important

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/* 定义级联层的顺序(越靠后优先级越高) */
@layer reset, base, components, utilities, overrides;

/* 在特定层级中定义样式 */
@layer reset {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
line-height: 1.5;
}
}

@layer base {
:root {
--primary: #3498db;
--text: #333;
}

body {
font-family: 'Inter', sans-serif;
color: var(--text);
background: #fff;
}
}

@layer components {
.button {
display: inline-block;
padding: 8px 16px;
background: var(--primary);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}

.card {
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
}

@layer utilities {
.text-center {
text-align: center;
}

.mt-1 {
margin-top: 8px;
}

.mt-2 {
margin-top: 16px;
}
}

/* 嵌套 layer */
@layer framework {
@layer components {
.button {
/* framework 的按钮样式 */
}
}
}

/* 导入外部样式并放入特定层 */
@import url('reset.css') layer(reset);
@import url('components.css') layer(components);

4.2 @scope 作用域

@scope 是一项较新的 CSS 特性,允许将样式限制在 DOM 树的特定范围内,从而避免样式泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/* 基础用法:样式只应用于 .card 内部的子元素 */
@scope (.card) {
.title {
font-size: 1.5rem;
font-weight: bold;
color: var(--text-primary);
}

.body {
padding: 1rem;
line-height: 1.6;
}

.footer {
padding: 0.75rem 1rem;
border-top: 1px solid var(--border);
font-size: 0.875rem;
color: var(--text-secondary);
}
}

/* 带作用域限制:只应用于直接子元素 */
@scope (.card) to (.card-inner) {
.title {
/* 只作用于 .card 的直接子级,且不作用于 .card-inner 内部的 .title */
}
}

/* 嵌套作用域 */
@scope (.sidebar) {
.nav-item {
padding: 8px 16px;
}

@scope (.nav-section) {
.nav-item {
padding-left: 32px;
/* 更深的导航层级使用不同的缩进 */
}
}
}

/* 作用域与媒体查询结合 */
@scope (.card) {
.title {
font-size: 1.25rem;
}

@media (min-width: 768px) {
.title {
font-size: 1.75rem;
}
}
}

4.3 CSS 模块(构建工具层面)

CSS 模块是通过构建工具(如 Webpack、Vite)实现的样式作用域隔离,它会在构建时为每个类名生成唯一的哈希值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* styles.module.css */
.button {
background: #3498db;
color: white;
padding: 8px 16px;
border-radius: 4px;
}

.button-primary {
background: #2ecc71;
}

.container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
1
2
3
4
5
6
7
8
9
// 在 JavaScript 中导入
import styles from './styles.module.css';

// 使用样式
<div className={styles.container}>
<button className={`${styles.button} ${styles.buttonPrimary}`}>
点击我
</button>
</div>

构建后,类名会被转换为类似 .button_abc123 的格式,确保样式不会污染全局。

4.4 CSS-in-JS(运行时作用域)

CSS-in-JS 是在 JavaScript 中定义样式的方案,天然具有作用域隔离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 使用 styled-components
import styled from 'styled-components';

const Button = styled.button`
background: #3498db;
color: white;
padding: 8px 16px;
border-radius: 4px;
&:hover {
background: #2980b9;
}
`;

const PrimaryButton = styled(Button)`
background: #2ecc71;
&:hover {
background: #27ae60;
}
`;

// 使用
<Button>普通按钮</Button>
<PrimaryButton>主要按钮</PrimaryButton>
1
2
3
4
5
6
7
8
9
10
11
// 使用 Emotion
import { css } from '@emotion/react';

const buttonStyle = css`
background: #3498db;
color: white;
padding: 8px 16px;
border-radius: 4px;
`;

<button css={buttonStyle}>点击我</button>

第五章:设计系统与 CSS 编程

设计系统(Design System)是一套可复用的设计规范和组件库,而 CSS 变量是实现设计系统的最佳工具。

5.1 设计令牌(Design Tokens)

设计令牌是设计系统中最小、最基础的样式单元。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/* 基础设计令牌 */
:root {
/* ===== 颜色系统 ===== */
/* 品牌色 */
--color-brand-50: #ebf5fb;
--color-brand-100: #d6eaf8;
--color-brand-200: #aed6f1;
--color-brand-300: #85c1e9;
--color-brand-400: #5dade2;
--color-brand-500: #3498db;
--color-brand-600: #2e86c1;
--color-brand-700: #2874a6;
--color-brand-800: #21618c;
--color-brand-900: #1a5276;

/* 中性色 */
--color-neutral-50: #f8f9fa;
--color-neutral-100: #f1f3f4;
--color-neutral-200: #e9ecef;
--color-neutral-300: #dee2e6;
--color-neutral-400: #ced4da;
--color-neutral-500: #adb5bd;
--color-neutral-600: #6c757d;
--color-neutral-700: #495057;
--color-neutral-800: #343a40;
--color-neutral-900: #212529;

/* 语义色 */
--color-success: #2ecc71;
--color-warning: #f39c12;
--color-danger: #e74c3c;
--color-info: #3498db;

/* ===== 字体系统 ===== */
--font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-family-heading: 'Poppins', 'Inter', sans-serif;
--font-family-mono: 'JetBrains Mono', 'Fira Code', monospace;

--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-lg: 1.125rem; /* 18px */
--font-size-xl: 1.25rem; /* 20px */
--font-size-2xl: 1.5rem; /* 24px */
--font-size-3xl: 1.875rem; /* 30px */
--font-size-4xl: 2.25rem; /* 36px */
--font-size-5xl: 3rem; /* 48px */

--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--font-weight-extrabold: 800;

--line-height-tight: 1.25;
--line-height-normal: 1.5;
--line-height-loose: 1.75;

/* ===== 间距系统 ===== */
--space-0: 0;
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-7: 1.75rem; /* 28px */
--space-8: 2rem; /* 32px */
--space-9: 2.25rem; /* 36px */
--space-10: 2.5rem; /* 40px */
--space-11: 2.75rem; /* 44px */
--space-12: 3rem; /* 48px */
--space-14: 3.5rem; /* 56px */
--space-16: 4rem; /* 64px */
--space-20: 5rem; /* 80px */
--space-24: 6rem; /* 96px */

/* ===== 圆角系统 ===== */
--radius-none: 0;
--radius-sm: 0.125rem; /* 2px */
--radius-base: 0.25rem; /* 4px */
--radius-md: 0.375rem; /* 6px */
--radius-lg: 0.5rem; /* 8px */
--radius-xl: 0.75rem; /* 12px */
--radius-2xl: 1rem; /* 16px */
--radius-3xl: 1.5rem; /* 24px */
--radius-full: 9999px;

/* ===== 阴影系统 ===== */
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
--shadow-base: 0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.06);
--shadow-md: 0 10px 15px rgba(0, 0, 0, 0.1), 0 4px 6px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 20px 25px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.04);
--shadow-xl: 0 25px 50px rgba(0, 0, 0, 0.15);
--shadow-2xl: 0 50px 100px rgba(0, 0, 0, 0.2);

/* ===== 过渡系统 ===== */
--transition-fast: 0.15s ease;
--transition-base: 0.3s ease;
--transition-slow: 0.5s ease;
--transition-slower: 0.8s ease;

/* ===== 容器宽度 ===== */
--container-sm: 640px;
--container-md: 768px;
--container-lg: 1024px;
--container-xl: 1280px;
--container-2xl: 1536px;
}

5.2 基于设计令牌的组件构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/* 使用设计令牌构建组件 */

/* ===== 按钮组件 ===== */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-5);
font-family: var(--font-family-base);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
line-height: var(--line-height-normal);
text-decoration: none;
border: 1px solid transparent;
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast);

&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}

/* 按钮变体 */
.btn-primary {
background: var(--color-brand-500);
color: white;

&:hover:not(:disabled) {
background: var(--color-brand-600);
}

&:active:not(:disabled) {
background: var(--color-brand-700);
}
}

.btn-secondary {
background: var(--color-neutral-200);
color: var(--color-neutral-800);

&:hover:not(:disabled) {
background: var(--color-neutral-300);
}
}

.btn-success {
background: var(--color-success);
color: white;

&:hover:not(:disabled) {
filter: brightness(0.9);
}
}

.btn-danger {
background: var(--color-danger);
color: white;

&:hover:not(:disabled) {
filter: brightness(0.9);
}
}

/* 按钮尺寸 */
.btn-sm {
padding: var(--space-1) var(--space-3);
font-size: var(--font-size-sm);
}

.btn-lg {
padding: var(--space-3) var(--space-7);
font-size: var(--font-size-lg);
}

/* ===== 卡片组件 ===== */
.card {
background: white;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-base);
overflow: hidden;
transition: box-shadow var(--transition-base);

&:hover {
box-shadow: var(--shadow-lg);
}
}

.card-header {
padding: var(--space-5) var(--space-5) var(--space-3);
border-bottom: 1px solid var(--color-neutral-200);
}

.card-title {
font-family: var(--font-family-heading);
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
color: var(--color-neutral-900);
margin: 0;
}

.card-body {
padding: var(--space-5);
}

.card-footer {
padding: var(--space-3) var(--space-5);
background: var(--color-neutral-50);
border-top: 1px solid var(--color-neutral-200);
}

/* ===== 输入框组件 ===== */
.input {
display: block;
width: 100%;
padding: var(--space-2) var(--space-3);
font-family: var(--font-family-base);
font-size: var(--font-size-base);
line-height: var(--line-height-normal);
color: var(--color-neutral-800);
background: white;
border: 1px solid var(--color-neutral-300);
border-radius: var(--radius-md);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);

&:focus {
outline: none;
border-color: var(--color-brand-500);
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.15);
}

&::placeholder {
color: var(--color-neutral-400);
}

&:disabled {
background: var(--color-neutral-100);
cursor: not-allowed;
}
}

5.3 主题系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/* ===== 主题系统 ===== */

/* 基础主题(浅色) */
:root,
[data-theme="light"] {
--theme-bg: #ffffff;
--theme-text: #212529;
--theme-text-secondary: #6c757d;
--theme-card-bg: #ffffff;
--theme-card-border: #e9ecef;
--theme-input-bg: #ffffff;
--theme-input-border: #ced4da;
--theme-hover: #f8f9fa;
--theme-shadow: 0 4px 6px rgba(0, 0, 0, 0.07);
--theme-shadow-lg: 0 20px 25px rgba(0, 0, 0, 0.1);
--theme-scrollbar-track: #f1f3f4;
--theme-scrollbar-thumb: #ced4da;
}

/* 暗色主题 */
[data-theme="dark"] {
--theme-bg: #121212;
--theme-text: #e4e4e4;
--theme-text-secondary: #a0a0a0;
--theme-card-bg: #1e1e1e;
--theme-card-border: #333333;
--theme-input-bg: #2d2d2d;
--theme-input-border: #404040;
--theme-hover: #2d2d2d;
--theme-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
--theme-shadow-lg: 0 20px 25px rgba(0, 0, 0, 0.4);
--theme-scrollbar-track: #2d2d2d;
--theme-scrollbar-thumb: #555555;
}

/* 高对比度主题 */
[data-theme="high-contrast"] {
--theme-bg: #ffffff;
--theme-text: #000000;
--theme-text-secondary: #000000;
--theme-card-bg: #ffffff;
--theme-card-border: #000000;
--theme-input-bg: #ffffff;
--theme-input-border: #000000;
--theme-hover: #f0f0f0;
--theme-shadow: none;
--theme-shadow-lg: none;
}

/* 使用主题变量的组件 */
body {
background: var(--theme-bg);
color: var(--theme-text);
transition: background var(--transition-slow), color var(--transition-slow);
}

.card {
background: var(--theme-card-bg);
border: 1px solid var(--theme-card-border);
box-shadow: var(--theme-shadow);
}

.input {
background: var(--theme-input-bg);
border-color: var(--theme-input-border);
color: var(--theme-text);
}

/* 暗色模式下的滚动条 */
[data-theme="dark"] {
scrollbar-color: var(--theme-scrollbar-thumb) var(--theme-scrollbar-track);
scrollbar-width: thin;
}

第六章:CSS 与 JavaScript 的协同编程

6.1 通过 CSS 变量动态控制样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// 完整的主题切换系统
class ThemeManager {
constructor() {
this.root = document.documentElement;
this.currentTheme = this.loadTheme() || 'light';
this.themes = {
light: {
'theme-bg': '#ffffff',
'theme-text': '#212529',
'theme-text-secondary': '#6c757d',
'theme-card-bg': '#ffffff',
'theme-card-border': '#e9ecef',
'theme-input-bg': '#ffffff',
'theme-input-border': '#ced4da',
'theme-hover': '#f8f9fa',
'theme-shadow': '0 4px 6px rgba(0,0,0,0.07)',
'theme-shadow-lg': '0 20px 25px rgba(0,0,0,0.1)',
},
dark: {
'theme-bg': '#121212',
'theme-text': '#e4e4e4',
'theme-text-secondary': '#a0a0a0',
'theme-card-bg': '#1e1e1e',
'theme-card-border': '#333333',
'theme-input-bg': '#2d2d2d',
'theme-input-border': '#404040',
'theme-hover': '#2d2d2d',
'theme-shadow': '0 4px 6px rgba(0,0,0,0.3)',
'theme-shadow-lg': '0 20px 25px rgba(0,0,0,0.4)',
},
'high-contrast': {
'theme-bg': '#ffffff',
'theme-text': '#000000',
'theme-text-secondary': '#000000',
'theme-card-bg': '#ffffff',
'theme-card-border': '#000000',
'theme-input-bg': '#ffffff',
'theme-input-border': '#000000',
'theme-hover': '#f0f0f0',
'theme-shadow': 'none',
'theme-shadow-lg': 'none',
}
};

this.applyTheme(this.currentTheme);
this.setupSystemPreferenceListener();
}

loadTheme() {
return localStorage.getItem('theme');
}

saveTheme(theme) {
localStorage.setItem('theme', theme);
}

applyTheme(theme) {
if (!this.themes[theme]) return;

// 设置 data-theme 属性(用于 CSS 选择器)
this.root.setAttribute('data-theme', theme);

// 通过 CSS 变量应用主题
const themeVars = this.themes[theme];
Object.entries(themeVars).forEach(([key, value]) => {
this.root.style.setProperty(`--${key}`, value);
});

this.currentTheme = theme;
this.saveTheme(theme);
this.dispatchThemeChange(theme);
}

toggleTheme() {
const themes = ['light', 'dark'];
const nextIndex = (themes.indexOf(this.currentTheme) + 1) % themes.length;
this.applyTheme(themes[nextIndex]);
}

getCurrentTheme() {
return this.currentTheme;
}

setupSystemPreferenceListener() {
// 监听系统主题变化
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
this.applyTheme(e.matches ? 'dark' : 'light');
}
});
}

dispatchThemeChange(theme) {
window.dispatchEvent(new CustomEvent('themeChange', { detail: { theme } }));
}
}

// 使用
const themeManager = new ThemeManager();

// 切换主题
document.querySelector('#theme-toggle').addEventListener('click', () => {
themeManager.toggleTheme();
});

// 监听主题变化
window.addEventListener('themeChange', (e) => {
console.log('主题已切换为:', e.detail.theme);
});

6.2 CSS 自定义属性与动画控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 动态控制 CSS 动画参数
class AnimationController {
constructor() {
this.root = document.documentElement;
this.defaults = {
duration: 0.3,
easing: 'ease',
delay: 0
};
}

setAnimation(selector, properties) {
const element = document.querySelector(selector);
if (!element) return;

// 通过 CSS 变量控制动画参数
const { duration, easing, delay } = { ...this.defaults, ...properties };

element.style.setProperty('--anim-duration', `${duration}s`);
element.style.setProperty('--anim-easing', easing);
element.style.setProperty('--anim-delay', `${delay}s`);
}

animateIn(selector) {
this.setAnimation(selector, { duration: 0.5, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' });
document.querySelector(selector)?.classList.add('anim-in');
}

animateOut(selector) {
this.setAnimation(selector, { duration: 0.3, easing: 'ease-in' });
document.querySelector(selector)?.classList.add('anim-out');
}
}

// CSS 中对应的动画定义
/*
.anim-in {
animation: fadeInUp var(--anim-duration, 0.3s) var(--anim-easing, ease) var(--anim-delay, 0s) forwards;
}

.anim-out {
animation: fadeOutDown var(--anim-duration, 0.3s) var(--anim-easing, ease) var(--anim-delay, 0s) forwards;
}

@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

@keyframes fadeOutDown {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(20px);
}
}
*/

6.3 响应式断点的 JavaScript 管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 将 CSS 断点暴露给 JavaScript
class BreakpointManager {
constructor() {
this.breakpoints = {
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
'2xl': 1536
};

this.current = this.detect();
this.setupListener();
}

detect() {
const width = window.innerWidth;
let current = 'xs';

for (const [name, size] of Object.entries(this.breakpoints)) {
if (width >= size) {
current = name;
}
}

return current;
}

setupListener() {
let timeout;
window.addEventListener('resize', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
const newBreakpoint = this.detect();
if (newBreakpoint !== this.current) {
this.current = newBreakpoint;
window.dispatchEvent(new CustomEvent('breakpointChange', {
detail: { breakpoint: this.current }
}));
}
}, 100);
});
}

isAbove(breakpoint) {
const sizes = Object.values(this.breakpoints);
const currentWidth = window.innerWidth;
return currentWidth >= (this.breakpoints[breakpoint] || 0);
}

isBelow(breakpoint) {
const sizes = Object.values(this.breakpoints);
const currentWidth = window.innerWidth;
return currentWidth < (this.breakpoints[breakpoint] || 0);
}
}

// 使用
const breakpoints = new BreakpointManager();

// 监听断点变化
window.addEventListener('breakpointChange', (e) => {
console.log('当前断点:', e.detail.breakpoint);
});

// 条件性执行
if (breakpoints.isAbove('lg')) {
// 在大屏幕上执行特定逻辑
}

6.4 高性能动画与 requestAnimationFrame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 使用 requestAnimationFrame 驱动 CSS 变量动画
class VariableAnimation {
constructor(element, variableName, config) {
this.element = element;
this.variableName = variableName;
this.config = {
from: 0,
to: 1,
duration: 1000,
easing: this.easeInOut,
...config
};
this.running = false;
}

easeInOut(t) {
return t < 0.5
? 4 * t * t * t
: 1 - Math.pow(-2 * t + 2, 3) / 2;
}

start() {
if (this.running) return;
this.running = true;
this.startTime = performance.now();
this.animate();
}

animate() {
if (!this.running) return;

const elapsed = performance.now() - this.startTime;
const progress = Math.min(elapsed / this.config.duration, 1);
const easedProgress = this.config.easing(progress);

const value = this.config.from + (this.config.to - this.config.from) * easedProgress;
this.element.style.setProperty(this.variableName, value);

if (progress < 1) {
requestAnimationFrame(() => this.animate());
} else {
this.running = false;
this.element.style.setProperty(this.variableName, this.config.to);
}
}

stop() {
this.running = false;
}
}

// 使用示例
const element = document.querySelector('.progress-bar');
const anim = new VariableAnimation(element, '--progress', {
from: 0,
to: 100,
duration: 2000
});
anim.start();

第七章:CSS 最新演进特性

7.1 CSS 嵌套(Nesting)

CSS 嵌套模块让样式表结构更清晰,更接近 HTML 结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/* 传统写法 */
.card { padding: 20px; }
.card .card-title { font-size: 1.5rem; }
.card .card-title:hover { color: blue; }
.card .card-body { padding: 16px; }

/* 嵌套写法 */
.card {
padding: 20px;

.card-title {
font-size: 1.5rem;

&:hover {
color: blue;
}
}

.card-body {
padding: 16px;
}
}

/* 更复杂的嵌套 */
.nav {
display: flex;
gap: 8px;

&-list {
list-style: none;
padding: 0;
margin: 0;
}

&-item {
display: inline-block;

&.active {
background: var(--primary);
color: white;
}
}

&-link {
text-decoration: none;
padding: 8px 16px;
display: block;

&:hover {
background: var(--hover-bg);
}

&::after {
content: '→';
margin-left: 4px;
}
}
}

/* 嵌套 @media 查询 */
.card {
display: flex;
flex-direction: column;

@media (min-width: 768px) {
flex-direction: row;

.card-image {
flex: 0 0 40%;
}
}
}

7.2 CSS 作用域(Scoping)

@scope 规则允许精确控制样式的作用范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 基础作用域 */
@scope (.component) {
.title { font-weight: bold; }
.body { padding: 1rem; }
}

/* 带限制的作用域 */
@scope (.card) to (.card-inner) {
.title {
/* 只应用于 .card 的直接子级 .title,不包括 .card-inner 内部的 */
}
}

/* 作用域的嵌套 */
@scope (.sidebar) {
.nav-item { padding: 8px; }

@scope (.nav-section) {
.nav-item { padding-left: 24px; }
}
}

7.3 CSS 函数与混入(Functions and Mixins)

CSS Functions and Mixins 模块(W3C 工作草案)将带来真正的"代码复用"能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* 定义自定义函数(草案语法) */
@function --fluid-font(--min, --pref, --max) {
result: clamp(var(--min), var(--pref), var(--max));
}

/* 使用自定义函数 */
.heading {
font-size: --fluid-font(1.5rem, 4vw, 3rem);
}

/* 定义混入(Mixin) */
@mixin --card-styles(--bg, --radius, --shadow) {
background: var(--bg);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 1.5rem;
}

/* 使用混入 */
.card-primary {
@apply --card-styles(white, 8px, var(--shadow-md));
}

.card-dark {
@apply --card-styles(#1a1a1a, 12px, var(--shadow-lg));
}

7.4 CSS 滚动驱动动画(Scroll-driven Animations)

滚动驱动动画允许动画由滚动进度控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* 滚动进度驱动的动画 */
@keyframes progress {
from { width: 0%; }
to { width: 100%; }
}

.progress-bar {
animation: progress linear;
animation-timeline: scroll(root);
animation-range: contain;
}

/* 滚动进入视口触发动画 */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}

.anim-on-scroll {
animation: fadeIn ease both;
animation-timeline: view();
animation-range: entry 0% entry 100%;
}

/* 固定元素跟随滚动 */
.sticky-element {
position: sticky;
top: 20px;
animation: sticky-rotate linear;
animation-timeline: scroll(root);
animation-range: contain;
}

@keyframes sticky-rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

7.5 CSS 视图过渡(View Transitions)

视图过渡 API 允许在页面状态变化时创建平滑的动画过渡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 定义视图过渡 */
::view-transition-old(root) {
animation: fadeOut 0.3s ease forwards;
}

::view-transition-new(root) {
animation: fadeIn 0.3s ease forwards;
}

@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}

@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}

/* 命名过渡 */
.smooth-transition {
view-transition-name: main-content;
}

7.6 CSS :has() 选择器

:has() 选择器允许根据子元素或后续兄弟元素来选择父元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* 选择包含图片的卡片 */
.card:has(img) {
padding: 0;
}

/* 选择包含特定标题的卡片 */
.card:has(.card-title) {
background: var(--highlight-bg);
}

/* 选择包含按钮的容器 */
.container:has(.btn-primary) {
border-color: var(--primary);
}

/* 选择有输入错误的表单 */
.form-group:has(.input:invalid) {
.form-label {
color: var(--danger);
}
}

/* 选择包含特定内容的文章 */
.article:has(h2:nth-of-type(3)) {
/* 包含第三个 h2 的文章 */
}

/* 选择有兄弟元素 .sidebar 的 main */
main:has(~ .sidebar) {
grid-template-columns: 1fr 300px;
}

/* 选择被选中的复选框的父标签 */
label:has(input:checked) {
font-weight: bold;
color: var(--primary);
}

第八章:CSS 性能优化与最佳实践

8.1 选择器性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 性能较差:通配选择器 + 后代选择器 */
* .item { /* 需要检查所有元素 */ }

/* 性能较好:直接类选择器 */
.item { /* 直接匹配,效率高 */ }

/* 性能较差:复杂伪类 */
ul li:last-child { /* 需要遍历子元素 */ }

/* 性能较好:使用类 + 结构伪类结合 */
.item:last-child { /* 更直接的匹配 */ }

/* 性能较差:属性选择器(使用频繁) */
input[type="text"] { /* 属性匹配较慢 */ }

/* 性能较好:为特定类型添加类 */
.input-text { /* 类匹配较快 */ }

8.2 重排与重绘优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 触发重排(Reflow)的属性:width, height, margin, padding, display, position, float, top, left 等 */
/* 触发重绘(Repaint)的属性:color, background, visibility, outline 等 */
/* 不触发重排和重绘的属性:transform, opacity 等 */

/* 推荐:使用 transform 和 opacity 做动画 */
.element {
transition: transform 0.3s ease, opacity 0.3s ease;
}

.element:hover {
transform: scale(1.05);
opacity: 0.9;
}

/* 避免:使用触发重排的属性做动画 */
.element {
transition: width 0.3s ease, height 0.3s ease;
}

.element:hover {
width: 120px;
height: 120px;
}

8.3 高效使用 CSS 变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 推荐:在需要动态变化的地方使用变量 */
:root {
--color: #3498db;
}

.button {
background: var(--color);
/* 变量值变化时,只触发重绘,不触发重排 */
}

/* 避免:在触发重排的属性上过度使用变量 */
:root {
--width: 100px;
--height: 100px;
--margin: 10px;
}

.element {
/* 这些属性变化会触发重排 */
width: var(--width);
height: var(--height);
margin: var(--margin);
}

8.4 CSS 代码组织最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/* 1. 使用 @layer 组织代码层级 */
@layer reset, base, components, utilities;

/* 2. 文件结构(推荐) */
/* styles/
├── tokens/
│ ├── colors.css
│ ├── typography.css
│ └── spacing.css
├── base/
│ ├── reset.css
│ ├── typography.css
│ └── forms.css
├── components/
│ ├── button.css
│ ├── card.css
│ └── modal.css
├── layouts/
│ ├── header.css
│ ├── footer.css
│ └── grid.css
└── utilities/
├── spacing.css
├── text.css
└── display.css
*/

/* 3. 命名约定(BEM) */
/* Block */
.card { /* ... */ }

/* Element */
.card__title { /* ... */ }
.card__body { /* ... */ }
.card__footer { /* ... */ }

/* Modifier */
.card--featured { /* ... */ }
.card__title--large { /* ... */ }

/* 4. 响应式设计模式 */
/* 移动优先 */
.element {
/* 移动端样式(默认) */
padding: 8px;
font-size: 14px;
}

@media (min-width: 768px) {
.element {
/* 平板样式 */
padding: 16px;
font-size: 16px;
}
}

@media (min-width: 1024px) {
.element {
/* 桌面样式 */
padding: 24px;
font-size: 18px;
}
}

总结

CSS 已经从单纯的“描述性语言”演变成为一种工程化的设计语言。它拥有了变量、运算、条件逻辑、模块化等编程语言的特性,让开发者能够构建复杂、可维护、可扩展的样式系统。

本文完整系统地讲解了 CSS 编程的各个方面:

  • CSS 变量:实现了样式的存储、复用和动态控制
  • 运算与函数:提供了数学计算、颜色处理等能力
  • 条件逻辑:通过 @media@supports@container 实现了响应式和特性检测
  • 模块化与作用域:通过 @layer@scope、CSS 模块等实现了样式封装
  • 设计系统:构建了完整的令牌体系和主题系统
  • CSS 与 JavaScript 协同:实现了动态样式控制和主题切换
  • 最新演进特性:涵盖了嵌套、:has()、视图过渡等现代特性

“CSS 编程”的核心在于:用编程思维组织样式代码、用工程化手段管理设计系统、用声明式方式表达视觉逻辑。理解这一点,你就能真正驾驭 CSS,让它成为构建 Web 应用最得心应手的工具之一。