WinddSnow

CSS-Custom-Properties-and-Dark-Mode

字数统计: 2.8k阅读时长: 11 min
2026/07/29

第25课:CSS 变量(自定义属性)——--*var()、作用域与暗色模式

CSS 变量,正式名称为自定义属性(Custom Properties),是 CSS 原生的变量系统。与预处理器(Sass、Less)的变量不同,CSS 变量在浏览器中动态生效,可以响应级联、继承、媒体查询和 JavaScript 操作。它们是实现主题切换(暗色模式)、设计令牌(Design Tokens)和组件级复用的核心工具。本节课将深入讲解自定义属性的声明、使用、作用域规则,以及如何利用它们构建动态主题系统。


1. 自定义属性的声明与使用

自定义属性以 -- 开头,后跟属性名。它必须在一个 CSS 规则块内声明,且区分大小写。

1
2
3
4
5
:root {
--primary-color: #3f51b5;
--spacing-unit: 8px;
--border-radius: 4px;
}

--primary-color 就是一个自定义属性,它存储了一个颜色值。使用时,通过 var() 函数引用。

1
2
3
4
5
.button {
background-color: var(--primary-color);
padding: var(--spacing-unit) calc(var(--spacing-unit) * 2);
border-radius: var(--border-radius);
}

1.1 var() 函数的语法

1
var(<custom-property-name>, <fallback-value>?)
  • 第一个参数:自定义属性名称(必须包含 -- 前缀)。
  • 第二个参数(可选):回退值。当自定义属性未定义,或浏览器不支持自定义属性时,使用该回退值。
1
2
3
4
5
6
7
.button {
/* 如果 --primary-color 未定义,则使用 #333 */
color: var(--primary-color, #333);

/* 回退值本身也可以包含另一个 var() 调用,形成链式回退 */
background-color: var(--brand-color, var(--accent-color, #ccc));
}

注意var() 的回退值仅用于属性值缺失的情况,若属性值无效(如 --primary-color: 10px 被用作 color 值),浏览器会使用属性的继承值或初始值,而非回退值。

1.2 自定义属性的值类型

自定义属性可以存储任何有效的 CSS 值——颜色、尺寸、字符串、完整声明片段,甚至另一个变量的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
:root {
--color: #ff6b6b;
--length: 16px;
--shadow: 0 2px 8px rgba(0,0,0,0.1);
--gradient: linear-gradient(45deg, #667eea, #764ba2);
--transition: all 0.3s ease;
}

.card {
color: var(--color);
margin: var(--length);
box-shadow: var(--shadow);
background: var(--gradient);
transition: var(--transition);
}

2. 作用域与继承

2.1 自定义属性遵循 CSS 级联规则

自定义属性也是 CSS 属性,同样遵循优先级、继承和级联规则。在 :root 中声明的属性是全局的(被所有元素继承);在某个选择器中声明的属性仅对该选择器及其后代有效。

1
2
3
4
<div class="parent">
父级
<div class="child">子级</div>
</div>
1
2
3
4
5
6
.parent {
--text-color: blue;
}
.child {
color: var(--text-color); /* blue,继承自父级 */
}

2.2 组件级作用域

可以在组件选择器中重新定义变量,覆盖全局值,从而创建组件级样式变体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
:root {
--btn-bg: #e0e0e0;
--btn-color: #333;
}
.btn {
background: var(--btn-bg);
color: var(--btn-color);
}
.btn-primary {
--btn-bg: #3f51b5;
--btn-color: white;
}
.btn-danger {
--btn-bg: #e74c3c;
--btn-color: white;
}
1
2
3
<button class="btn">默认</button>
<button class="btn btn-primary">主要</button>
<button class="btn btn-danger">危险</button>

每个变体类仅重新定义变量,而不修改具体样式属性,保持了样式的单一来源和高度复用。

2.3 动态作用域与媒体查询

自定义属性可在 @media 内部重新定义,实现响应式样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
:root {
--grid-columns: 1fr;
}

@media (min-width: 768px) {
:root {
--grid-columns: repeat(2, 1fr);
}
}

@media (min-width: 1024px) {
:root {
--grid-columns: repeat(3, 1fr);
}
}

.grid {
display: grid;
grid-template-columns: var(--grid-columns);
}

3. JavaScript 操作自定义属性

CSS 自定义属性可以通过 JavaScript 动态读写,这是实现主题切换、用户偏好的核心能力。

3.1 getPropertyValuesetProperty

1
2
3
4
5
6
7
8
9
10
11
// 读取根元素的自定义属性
const rootStyle = getComputedStyle(document.documentElement);
const primaryColor = rootStyle.getPropertyValue('--primary-color').trim();
console.log(primaryColor); // "#3f51b5"

// 设置根元素的自定义属性(切换主题)
document.documentElement.style.setProperty('--primary-color', '#ff5722');

// 设置单个元素的属性
const card = document.querySelector('.card');
card.style.setProperty('--card-bg', '#f9f9f9');

3.2 批量更新与性能

直接操作 style 属性会触发元素的重绘。若需频繁更新多个变量(如跟随鼠标移动的渐变),建议使用 requestAnimationFrame 合并更新。

1
2
3
4
5
6
7
8
document.addEventListener('mousemove', (e) => {
requestAnimationFrame(() => {
const x = (e.clientX / window.innerWidth) * 100;
const y = (e.clientY / window.innerHeight) * 100;
document.documentElement.style.setProperty('--mouse-x', `${x}%`);
document.documentElement.style.setProperty('--mouse-y', `${y}%`);
});
});

4. 暗色模式:使用 CSS 变量 + 媒体查询实现主题切换

借助 prefers-color-scheme 媒体查询和自定义属性,可以构建自动跟随系统主题的暗色模式。

4.1 定义明暗主题的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
:root {
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #1a1a1a;
--text-secondary: #666666;
--border-color: #e0e0e0;
}

@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--text-primary: #e4e4e4;
--text-secondary: #a0a0a0;
--border-color: #2a2a4a;
}
}

4.2 在样式中使用变量

1
2
3
4
5
6
7
8
9
10
body {
background-color: var(--bg-primary);
color: var(--text-primary);
}

.card {
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
color: var(--text-primary);
}

这样,当用户在操作系统层面切换明暗模式时,页面会自动切换主题。

4.3 手动切换 + 系统偏好

为提供更好的用户体验,通常允许用户手动切换主题,并持久化选择。JavaScript 可以将选择存储到 localStorage,并动态在 :root 上设置 data-theme 属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const toggleBtn = document.getElementById('theme-toggle');
const root = document.documentElement;

// 从 localStorage 读取,或跟随系统
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
root.setAttribute('data-theme', 'dark');
} else if (savedTheme === 'light') {
root.setAttribute('data-theme', 'light');
}

toggleBtn.addEventListener('click', () => {
const current = root.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
root.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});

CSS 需配合 data-theme 属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
:root {
/* 默认跟随系统(上面用 @media 定义) */
}
:root[data-theme="light"] {
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #1a1a1a;
--text-secondary: #666666;
--border-color: #e0e0e0;
}
:root[data-theme="dark"] {
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--text-primary: #e4e4e4;
--text-secondary: #a0a0a0;
--border-color: #2a2a4a;
}

5. 自定义属性的进阶用法

5.1 与 calc() 结合动态计算

1
2
3
4
5
6
7
8
:root {
--spacing: 16px;
}

.section {
padding: calc(var(--spacing) * 2);
margin-bottom: calc(var(--spacing) * 1.5);
}

5.2 在动画中应用自定义属性

自定义属性的值可以随时间变化(通过 JavaScript 定时更新),但这不会自动产生动画过渡。如果需要动画,可以使用 @property(CSS Houdini)定义自定义属性的类型,使其可被动画化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@property --gradient-angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}

@keyframes rotate-gradient {
to { --gradient-angle: 360deg; }
}

.animated-border {
background: linear-gradient(var(--gradient-angle), #ff6b6b, #feca57);
animation: rotate-gradient 3s linear infinite;
}

注意@property 目前在现代浏览器中支持良好(Chrome 85+、Edge 85+、Safari 15.4+、Firefox 128+)。


课后练习

一、概念自测(选择题 / 填空题)

  1. (单选) CSS 自定义属性名称必须以什么开头?
    A. $
    B. @
    C. --
    D. var-

  2. (单选)var() 函数中,第二个参数的作用是?
    A. 设置变量的最小值。
    B. 当自定义属性未定义时使用的回退值。
    C. 当自定义属性值无效时使用的回退值。
    D. 声明该变量是否可继承。

  3. (填空) 要通过 JavaScript 设置根元素的自定义属性 --accent-colorred,应使用 document.documentElement.style.setProperty('______', 'red')

  4. (多选) 关于 CSS 自定义属性的说法,哪些是正确的?
    A. 它们遵循 CSS 级联和继承规则。
    B. 它们可以在 @media 查询中重新定义。
    C. 它们只能在 :root 中声明。
    D. 它们的值可以包含另一个 var() 引用。

二、AI 编程任务:编写面向 AI 的提示词

场景:你需要实现一个使用 CSS 自定义属性的按钮组件库,包含三种变体(默认、主要、危险),并支持暗色模式自动切换(跟随系统 prefers-color-scheme)。要求如下:

  • 定义全局变量:--btn-bg--btn-color--btn-radius,默认值分别为 #e0e0e0#3336px
  • 三种变体仅需覆盖 --btn-bg--btn-color,不修改其他属性。
  • 暗色模式下,--btn-bg 自动调整为深色值。
  • 提供一套简洁的 HTML 示例展示三种按钮。
  • 使用原生 CSS,无需预处理器。

任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的完整 HTML + CSS 代码。提示词中需明确指定自定义属性的命名、组件变体的实现策略、暗色模式的媒体查询写法。

三、Agent 模式下的提示词示例

在现代 AI 编程 Agent(如 Codex、Claude Code、OpenCode)中,你无需逐行描述 CSS,只需以架构和约束的方式描述目标,Agent 会自行生成文件、管理变量和选择器。

Agent 提示词

你是一个资深前端开发 Agent,使用 CSS 自定义属性实现一个按钮组件库和暗色模式主题系统。请完成以下任务:

  1. styles/theme.css 中定义全局设计令牌(Design Tokens):
    • 明色主题:--color-bg: #fff--color-text: #1a1a1a--color-primary: #3f51b5--color-danger: #e74c3c
    • 暗色主题:使用 prefers-color-scheme: dark 覆盖上述变量为暗色值。
  2. styles/button.css 中定义 .btn 基础样式,使用 var() 引用令牌。添加 .btn-primary.btn-danger 变体类,仅覆盖相关变量。
  3. index.html 中展示三个按钮,并引入 CSS 文件。
  4. 额外要求:在页面右上角添加一个“切换主题”按钮,通过 JS 在 <html> 上设置 data-theme 属性,手动切换明暗主题。JS 逻辑放在 scripts/theme-toggle.js
  5. 所有文件需遵循现代 CSS 最佳实践,代码整洁,适当注释。

课后练习答案

一、概念自测答案

  1. C

    • 解析:CSS 自定义属性必须以 -- 开头,例如 --main-color
  2. B

    • 解析:var() 的第二个参数是回退值,当自定义属性未定义时使用。注意区分“未定义”和“值无效”:值无效时回退值不起作用。
  3. --accent-color

    • 解析:setProperty 第一个参数需要完整的自定义属性名,包括 -- 前缀。
  4. A、B、D

    • 解析:A 正确,自定义属性遵循级联和继承;B 正确,可在媒体查询内重新定义;C 错误,可以在任何选择器中声明;D 正确,var() 可以嵌套引用。

二、AI 编程任务参考答案(提示词示例)

示例提示词
“请使用 CSS 自定义属性创建一个按钮系统。要求:

  • :root 中定义 --btn-bg: #e0e0e0--btn-color: #333--btn-radius: 6px--btn-padding: 8px 16px
  • .btn 基础类使用这些变量。
  • .btn-primary 覆盖 --btn-bg: #3f51b5; --btn-color: #fff;
  • .btn-danger 覆盖 --btn-bg: #e74c3c; --btn-color: #fff;
  • 添加 @media (prefers-color-scheme: dark) 将默认 --btn-bg 改为 #555--btn-color 改为 #eee
  • HTML 提供三个按钮:默认、主要、危险。
  • 代码注释清晰,直接输出完整 HTML 文件。”

三、Agent 模式下的提示词示例

(见上文 Agent 提示词部分)

CATALOG
  1. 1. 第25课:CSS 变量(自定义属性)——--*、var()、作用域与暗色模式
    1. 1.1. 1. 自定义属性的声明与使用
      1. 1.1.1. 1.1 var() 函数的语法
      2. 1.1.2. 1.2 自定义属性的值类型
    2. 1.2. 2. 作用域与继承
      1. 1.2.1. 2.1 自定义属性遵循 CSS 级联规则
      2. 1.2.2. 2.2 组件级作用域
      3. 1.2.3. 2.3 动态作用域与媒体查询
    3. 1.3. 3. JavaScript 操作自定义属性
      1. 1.3.1. 3.1 getPropertyValue 和 setProperty
      2. 1.3.2. 3.2 批量更新与性能
    4. 1.4. 4. 暗色模式:使用 CSS 变量 + 媒体查询实现主题切换
      1. 1.4.1. 4.1 定义明暗主题的变量
      2. 1.4.2. 4.2 在样式中使用变量
      3. 1.4.3. 4.3 手动切换 + 系统偏好
    5. 1.5. 5. 自定义属性的进阶用法
      1. 1.5.1. 5.1 与 calc() 结合动态计算
      2. 1.5.2. 5.2 在动画中应用自定义属性
    6. 1.6. 课后练习
      1. 1.6.1. 一、概念自测(选择题 / 填空题)
      2. 1.6.2. 二、AI 编程任务:编写面向 AI 的提示词
      3. 1.6.3. 三、Agent 模式下的提示词示例
    7. 1.7. 课后练习答案
      1. 1.7.1. 一、概念自测答案
      2. 1.7.2. 二、AI 编程任务参考答案(提示词示例)
      3. 1.7.3. 三、Agent 模式下的提示词示例