CSS 变量(自定义属性)
CSS 自定义属性(通常称为 CSS 变量)允许我们定义可复用的值,使样式表更易于维护和修改。本章将详细介绍 CSS 变量的完整用法。
什么是 CSS 变量?
CSS 自定义属性是一种特殊的 CSS 属性,用于存储值以便在文档中重复使用。它们以 -- 开头,通过 var() 函数引用。
为什么使用 CSS 变量?
传统 CSS 中,我们经常在多个地方重复相同的值:
/* 没有 CSS 变量时 */
.button {
background-color: #3498db;
color: white;
}
.link {
color: #3498db;
}
.border {
border-color: #3498db;
}
/* 如果要修改主题色,需要逐个替换 */
使用 CSS 变量后:
:root {
--primary-color: #3498db;
}
.button {
background-color: var(--primary-color);
color: white;
}
.link {
color: var(--primary-color);
}
.border {
border-color: var(--primary-color);
}
/* 只需修改变量值即可全局生效 */
CSS 变量的优势
| 优势 | 说明 |
|---|---|
| 减少重复 | 定义一次,多处使用 |
| 语义化 | --primary-color 比 #3498db 更容易理解 |
| 便于维护 | 修改一处即可全局更新 |
| 动态修改 | 可通过 JavaScript 实时修改 |
| 主题切换 | 轻松实现深色/浅色主题 |
变量的使用限制
虽然 CSS 变量非常强大,但有一些重要的使用限制需要注意:
1. 不能用于媒体查询和容器查询
/* ❌ 错误:变量不能用于媒体查询 */
:root {
--breakpoint: 768px;
}
/* 这不会工作 */
@media (min-width: var(--breakpoint)) {
.container { /* ... */ }
}
/* ❌ 错误:变量也不能用于容器查询 */
@container (min-width: var(--breakpoint)) {
.card { /* ... */ }
}
2. 不能用于属性名和选择器
/* ❌ 错误:变量不能用于属性名 */
.element {
var(--property-name): blue;
}
/* ❌ 错误:变量不能用于选择器 */
var(--class-name) {
color: red;
}
3. 可以用于属性值的任何部分
/* ✅ 正确:变量可以用于属性值 */
.element {
margin: var(--spacing);
margin: var(--spacing) auto;
box-shadow: 0 0 var(--blur) var(--color);
}
如果需要在媒体查询中使用变量,可以在 JavaScript 中读取变量值,然后动态添加类或样式:
const breakpoint = getComputedStyle(document.documentElement)
.getPropertyValue('--breakpoint');
// 使用 JavaScript 动态响应
const mediaQuery = window.matchMedia(`(min-width: ${breakpoint})`);
定义和使用变量
定义变量
CSS 变量使用 -- 前缀定义:
/* 基本语法 */
选择器 {
--变量名: 值;
}
/* 示例 */
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--font-size-base: 16px;
--spacing: 20px;
--border-radius: 8px;
--transition-speed: 0.3s;
}
使用变量
使用 var() 函数引用变量:
.button {
background-color: var(--primary-color);
padding: var(--spacing);
border-radius: var(--border-radius);
transition: all var(--transition-speed);
}
变量命名规则
- 必须以
--开头 - 区分大小写(
--my-color和--My-color是不同的变量) - 可以包含字母、数字、连字符和下划线
- 不能包含空格或特殊字符
:root {
/* 正确 */
--color: blue;
--primary-color: #3498db;
--my_color: green;
--color1: red;
--COLOR: yellow; /* 与 --color 不同 */
/* 错误 */
/* --1color: blue; 不能以数字开头 */
/* --my color: red; 不能包含空格 */
}
作用域和继承
全局作用域
在 :root 伪类中定义的变量具有全局作用域:
:root {
--global-color: #3498db;
}
/* 整个文档都可以使用 */
.any-element {
color: var(--global-color);
}
局部作用域
在特定选择器中定义的变量只在该选择器及其子元素中有效:
.card {
--card-padding: 20px;
padding: var(--card-padding); /* 有效 */
}
.card .content {
padding: var(--card-padding); /* 有效:子元素可以继承 */
}
.other-element {
padding: var(--card-padding); /* 无效:不在 .card 作用域内 */
}
作用域覆盖
内层选择器可以覆盖外层的变量:
:root {
--text-color: #333;
}
.card {
--text-color: #666; /* 覆盖全局变量 */
color: var(--text-color); /* #666 */
}
.card.special {
--text-color: #999; /* 进一步覆盖 */
color: var(--text-color); /* #999 */
}
继承示例
<div class="parent">
父元素文字
<div class="child">
子元素文字
</div>
</div>
.parent {
--main-color: blue;
color: var(--main-color); /* 蓝色 */
}
.child {
/* 继承父元素的变量值 */
color: var(--main-color); /* 蓝色 */
}
.child.override {
--main-color: red; /* 子元素重新定义 */
color: var(--main-color); /* 红色 */
}
回退值
基本回退
var() 函数可以接受第二个参数作为回退值:
.element {
/* 如果 --my-color 未定义,使用 #333 */
color: var(--my-color, #333);
/* 如果 --spacing 未定义,使用 10px */
padding: var(--spacing, 10px);
}
嵌套回退
可以嵌套使用变量作为回退值:
.element {
/* 尝试 --primary-color,如果未定义尝试 --theme-color,最后使用 blue */
color: var(--primary-color, var(--theme-color, blue));
}
无效值处理
当变量值无效时,CSS 会使用属性的初始值:
:root {
--invalid-color: 16px; /* 对 color 属性来说是无效值 */
}
.text {
color: var(--invalid-color);
/* 由于 16px 不是有效的颜色值,color 会使用初始值(通常是黑色) */
}
无效值的处理流程:
当浏览器遇到无效的 var() 替换时,会按以下顺序处理:
- 检查属性是否可继承,如果有父元素的值则使用
- 如果没有继承值,使用属性的初始值
:root {
--text-color: 16px; /* 无效的颜色值 */
}
.parent {
color: blue;
}
.child {
/* 由于 --text-color 无效,会继承父元素的 color: blue */
color: var(--text-color);
}
使用 @property 避免无效值问题:
@property --text-color {
syntax: '<color>';
inherits: true;
initial-value: #333;
}
:root {
--text-color: 16px; /* 无效值 */
}
.text {
color: var(--text-color);
/* 由于 @property 定义了类型,无效值会被忽略,使用 initial-value: #333 */
}
循环依赖
CSS 变量不能形成循环引用,否则会导致所有相关变量都被视为无效:
/* ❌ 错误:循环依赖 */
:root {
--a: var(--b);
--b: var(--a);
}
.element {
color: var(--a); /* 无效:循环依赖 */
}
/* ❌ 错误:自身引用 */
:root {
--self: var(--self);
}
.element {
color: var(--self); /* 无效:自身引用 */
}
实际场景中的循环依赖:
/* 这看起来可能没问题,但会造成问题 */
.button {
--button-bg: var(--button-color);
--button-color: var(--button-bg);
/* 循环依赖!两个变量都无效 */
}
循环依赖会导致所有参与循环的变量都变为无效值(CSS 关键字 guaranteed-invalid),最终使用属性的初始值或继承值。
@property 规则
@property 规则提供了一种更严谨的变量定义方式,可以指定类型、初始值和继承性。
基本语法
@property --property-name {
syntax: '<类型>';
inherits: true | false;
initial-value: 初始值;
}
属性说明
| 属性 | 说明 |
|---|---|
syntax | 定义变量值的数据类型 |
inherits | 是否继承父元素的值 |
initial-value | 初始值 |
支持的类型
@property 规则的 syntax 描述符支持多种数据类型:
基本数据类型:
| 类型 | 说明 | 示例值 |
|---|---|---|
<angle> | 角度值 | 45deg, 0.5turn |
<color> | 颜色值 | #3498db, rgb(52, 152, 219) |
<custom-ident> | 自定义标识符 | my-value |
<image> | 图片值 | url('image.png') |
<integer> | 整数 | 1, 42 |
<length> | 长度值 | 10px, 2em |
<length-percentage> | 长度或百分比 | 10px, 50% |
<number> | 数字 | 1.5, 0.8 |
<percentage> | 百分比 | 50%, 100% |
<resolution> | 分辨率 | 96dpi, 2dppx |
<string> | 字符串值 | "hello", 'world' |
<time> | 时间值 | 0.3s, 200ms |
<transform-function> | 变换函数 | rotate(45deg) |
<transform-list> | 变换函数列表 | rotate(45deg) scale(1.5) |
<url> | URL 值 | url('path') |
类型组合语法:
/* 使用 | 表示"或"关系 */
@property --size {
syntax: '<length> | <percentage>';
inherits: false;
initial-value: 0;
}
/* 使用 + 表示空格分隔的列表 */
@property --colors {
syntax: '<color>+';
inherits: false;
initial-value: black;
}
/* 使用 # 表示逗号分隔的列表 */
@property --values {
syntax: '<number>#';
inherits: false;
initial-value: 0;
}
/* 通配符语法(接受任何值) */
@property --anything {
syntax: '*';
inherits: false;
/* 使用 * 时可以省略 initial-value */
}
完整示例:
/* 颜色类型 */
@property --my-color {
syntax: '<color>';
inherits: true;
initial-value: #000000;
}
/* 长度类型 */
@property --my-length {
syntax: '<length>';
inherits: true;
initial-value: 0px;
}
/* 百分比类型 */
@property --my-percent {
syntax: '<percentage>';
inherits: false;
initial-value: 0%;
}
/* 数字类型 */
@property --my-number {
syntax: '<number>';
inherits: true;
initial-value: 0;
}
/* 角度类型(适合动画) */
@property --my-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
/* 时间类型 */
@property --my-duration {
syntax: '<time>';
inherits: false;
initial-value: 0s;
}
/* 字符串类型 */
@property --my-string {
syntax: '<string>';
inherits: true;
initial-value: "";
}
/* 变换函数列表(支持多个变换组合) */
@property --my-transform {
syntax: '<transform-list>';
inherits: false;
initial-value: none;
}
@property 的优势
类型验证:
@property --box-color {
syntax: '<color>';
inherits: false;
initial-value: blue;
}
.element {
--box-color: red; /* 有效 */
--box-color: 20px; /* 无效,使用初始值 blue */
background: var(--box-color);
}
控制继承:
@property --theme-color {
syntax: '<color>';
inherits: false; /* 不继承 */
initial-value: #3498db;
}
.parent {
--theme-color: red;
}
.child {
/* 由于 inherits: false,这里使用初始值 #3498db */
background: var(--theme-color);
}
支持动画:
使用 @property 定义的变量可以参与 CSS 动画:
@property --angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
@keyframes rotate {
to {
--angle: 360deg;
}
}
.element {
animation: rotate 2s linear infinite;
background: linear-gradient(var(--angle), red, blue);
}
普通的 CSS 变量值对浏览器来说是"不透明"的——浏览器不知道它是什么类型的数据,因此无法在两个值之间进行插值。使用 @property 定义类型后,浏览器就知道如何进行插值计算,从而实现平滑的动画效果。
动画进度条示例:
@property --progress {
syntax: '<percentage>';
inherits: false;
initial-value: 0%;
}
.progress-bar {
width: 100%;
height: 20px;
background: linear-gradient(
to right,
#3498db var(--progress),
#e0e0e0 var(--progress)
);
animation: fill 3s ease-in-out forwards;
}
@keyframes fill {
to {
--progress: 100%;
}
}
@property 规则的注意事项
1. 必需的描述符
@property 规则必须包含 syntax 和 inherits 描述符,否则整个规则无效:
/* ❌ 错误:缺少 inherits */
@property --my-color {
syntax: '<color>';
initial-value: blue;
}
/* ✅ 正确:包含所有必需描述符 */
@property --my-color {
syntax: '<color>';
inherits: true;
initial-value: blue;
}
2. initial-value 的计算独立性
如果 syntax 不是通配符 *,initial-value 必须是"计算独立"的值:
/* ✅ 正确:计算独立的值 */
@property --length {
syntax: '<length>';
inherits: false;
initial-value: 10px; /* 不依赖其他值 */
}
/* ❌ 错误:依赖上下文的值 */
@property --length {
syntax: '<length>';
inherits: false;
initial-value: 2em; /* em 依赖父元素的 font-size */
}
3. 通配符语法可以省略 initial-value
/* ✅ 正确:使用 * 语法时可以省略 initial-value */
@property --anything {
syntax: '*';
inherits: false;
}
实际应用场景
1. 主题切换
CSS 变量非常适合实现主题切换:
/* 浅色主题(默认) */
:root {
--bg-color: #ffffff;
--text-color: #333333;
--primary-color: #3498db;
--secondary-color: #2ecc71;
--border-color: #dddddd;
--card-bg: #f5f5f5;
}
/* 深色主题 */
[data-theme="dark"] {
--bg-color: #1a1a1a;
--text-color: #ffffff;
--primary-color: #5dade2;
--secondary-color: #58d68d;
--border-color: #444444;
--card-bg: #2a2a2a;
}
/* 应用变量 */
body {
background-color: var(--bg-color);
color: var(--text-color);
}
.card {
background-color: var(--card-bg);
border: 1px solid var(--border-color);
}
.button {
background-color: var(--primary-color);
color: white;
}
JavaScript 切换主题:
// 切换主题
function toggleTheme() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
html.setAttribute('data-theme', currentTheme === 'dark' ? 'light' : 'dark');
}
2. 响应式设计
结合媒体查询实现响应式变量:
:root {
--container-width: 100%;
--spacing: 10px;
--font-size-base: 14px;
}
@media (min-width: 768px) {
:root {
--container-width: 750px;
--spacing: 20px;
--font-size-base: 16px;
}
}
@media (min-width: 1024px) {
:root {
--container-width: 970px;
--spacing: 30px;
--font-size-base: 18px;
}
}
.container {
max-width: var(--container-width);
padding: var(--spacing);
}
body {
font-size: var(--font-size-base);
}
3. 组件样式
创建可配置的组件:
/* 定义组件变量及默认值 */
.card {
--card-bg: white;
--card-border-color: #ddd;
--card-padding: 20px;
--card-radius: 8px;
--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
background: var(--card-bg);
border: 1px solid var(--card-border-color);
padding: var(--card-padding);
border-radius: var(--card-radius);
box-shadow: var(--card-shadow);
}
/* 覆盖变量创建变体 */
.card.dark {
--card-bg: #2a2a2a;
--card-border-color: #444;
}
.card.compact {
--card-padding: 10px;
--card-radius: 4px;
}
.card.elevated {
--card-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
4. 间距系统
建立统一的间距系统:
:root {
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
}
/* 使用 */
.section {
padding: var(--spacing-xl) var(--spacing-lg);
}
.button {
padding: var(--spacing-sm) var(--spacing-md);
}
.gap {
gap: var(--spacing-md);
}
5. 字体系统
建立字体层级:
:root {
--font-family-base: 'Helvetica Neue', Arial, sans-serif;
--font-family-mono: 'SF Mono', Consolas, 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: 2rem; /* 32px */
--line-height-tight: 1.25;
--line-height-base: 1.5;
--line-height-relaxed: 1.75;
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-bold: 700;
}
body {
font-family: var(--font-family-base);
font-size: var(--font-size-base);
line-height: var(--line-height-base);
}
h1 {
font-size: var(--font-size-3xl);
font-weight: var(--font-weight-bold);
line-height: var(--line-height-tight);
}
code {
font-family: var(--font-family-mono);
font-size: var(--font-size-sm);
}
6. 断点系统
虽然 CSS 变量不能直接用于媒体查询,但可以用于相关的样式:
:root {
--columns: 1;
}
@media (min-width: 768px) {
:root {
--columns: 2;
}
}
@media (min-width: 1024px) {
:root {
--columns: 3;
}
}
.grid {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
}
JavaScript 操作变量
读取变量
// 获取元素的计算样式
const element = document.querySelector('.element');
const styles = getComputedStyle(element);
// 读取 CSS 变量
const primaryColor = styles.getPropertyValue('--primary-color');
console.log(primaryColor); // '#3498db'
设置变量
// 在元素上设置变量
const element = document.querySelector('.element');
element.style.setProperty('--custom-color', 'red');
// 在根元素上设置变量(全局生效)
document.documentElement.style.setProperty('--primary-color', '#e74c3c');
动态主题切换示例
const themes = {
light: {
'--bg-color': '#ffffff',
'--text-color': '#333333',
'--primary-color': '#3498db'
},
dark: {
'--bg-color': '#1a1a1a',
'--text-color': '#ffffff',
'--primary-color': '#5dade2'
}
};
function setTheme(themeName) {
const theme = themes[themeName];
const root = document.documentElement;
for (const [property, value] of Object.entries(theme)) {
root.style.setProperty(property, value);
}
// 保存偏好
localStorage.setItem('theme', themeName);
}
// 加载保存的主题
const savedTheme = localStorage.getItem('theme') || 'light';
setTheme(savedTheme);
最佳实践
1. 命名规范
:root {
/* 使用语义化名称 */
--primary-color: #3498db; /* 好的命名 */
--blue: #3498db; /* 不好的命名 */
/* 使用前缀分组 */
--color-primary: #3498db;
--color-secondary: #2ecc71;
--color-text: #333;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--font-size-base: 16px;
--font-size-lg: 20px;
}
2. 组织变量
:root {
/* 颜色 */
--color-primary: #3498db;
--color-secondary: #2ecc71;
--color-text: #333;
--color-bg: #fff;
/* 字体 */
--font-family-base: sans-serif;
--font-size-base: 16px;
/* 间距 */
--spacing-base: 16px;
/* 其他 */
--border-radius: 8px;
--transition-speed: 0.3s;
}
3. 使用 CSS 变量文件
将变量集中管理:
/* variables.css */
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
/* ... */
}
/* main.css */
@import 'variables.css';
/* 使用变量 */
.button {
background: var(--primary-color);
}
4. 提供回退值
/* 为重要变量提供回退值 */
.element {
color: var(--text-color, #333);
padding: var(--spacing, 16px);
}
5. 合理使用 @property
/* ✅ 推荐:需要动画或类型约束时使用 @property */
@property --theme-color {
syntax: '<color>';
inherits: true;
initial-value: #3498db;
}
/* ✅ 推荐:普通变量使用简单语法即可 */
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
}
6. 避免过度嵌套回退
/* ❌ 不推荐:过度嵌套影响性能和可读性 */
.element {
color: var(--a, var(--b, var(--c, var(--d, black))));
}
/* ✅ 推荐:提供一两个合理的回退值 */
.element {
color: var(--primary-color, var(--fallback-color, #333));
}
浏览器兼容性
CSS 变量在现代浏览器中得到了广泛支持。如果需要支持旧浏览器:
/* 提供回退 */
.element {
color: #333; /* 旧浏览器回退 */
color: var(--text-color, #333); /* 现代浏览器 */
}
/* 使用 @supports 检测 */
@supports (--css: variables) {
.element {
color: var(--text-color);
}
}
小结
本章学习了:
- 变量定义和使用:
--前缀定义,var()函数使用 - 作用域和继承:全局作用域、局部作用域、继承机制
- 回退值:
var()函数的第二个参数 - @property 规则:类型定义、继承控制、动画支持
- 实际应用:主题切换、响应式设计、组件样式、间距系统
- JavaScript 操作:读取和设置 CSS 变量
练习
- 创建一个简单的主题切换系统
- 使用 CSS 变量建立一套间距系统
- 使用 @property 创建一个可动画的颜色变量
- 用 JavaScript 实现动态修改主题色
- 创建一个可配置的卡片组件