CSS 自定义属性(CSS 变量)
CSS 变量是 CSS 的编程基础设施
CSS 变量(CSS Custom Properties)让 CSS 具备了编程能力。通过定义可复用的命名值,我们可以创建主题系统、实现响应式设计、在运行时动态修改样式,而不需要 JavaScript 操作 style 对象。
理解 CSS 变量的层叠规则、继承机制和计算过程,是构建现代设计系统的基础。
面试定位:CSS 变量是现代 CSS 开发的核心。面试官通过变量的层叠覆盖、var() 的备选值机制、以及变量与预处理器变量(如 Sass $var)的区别,问的是候选人对 CSS 机制的深层理解。
自定义属性的基础语法
定义和引用
/* 定义:-- 开头,冒号分隔 */
:root {
--color-primary: #3b82f6;
--spacing-md: 1rem;
--font-size-base: 16px;
--border-radius: 8px;
}
/* 引用:var() 函数 */
.button {
background: var(--color-primary);
padding: var(--spacing-md);
font-size: var(--font-size-base);
border-radius: var(--border-radius);
}
命名规范
/* 合法命名 */
--color-primary: blue;
--my-var: 10px;
--MY_VAR: 20px; /* 大小写敏感 */
--font-size-lg: 1.5rem;
/* 非法命名(以数字开头) */
/* --1color: blue; ❌ */
/* 推荐命名规范 */
--brand-primary: #3b82f6;
--space-unit: 8px;
--radius-small: 4px;
--radius-medium: 8px;
--radius-large: 16px;
var() 函数详解
基本用法
/* 直接引用 */
.element {
color: var(--color-text);
margin: var(--spacing-md);
}
/* 带备选值 */
.element {
color: var(--color-text, #333); /* 变量未定义时使用备选值 */
margin: var(--spacing-md, 1rem); /* 备选值可以是任何有效值 */
}
备选值机制
/* 备选值可以是多个 */
.element {
/* 尝试 --color-link,其次 --color-primary,最后 #0066cc */
color: var(--color-link, var(--color-primary, #0066cc));
}
/* 备选值支持复杂表达式 */
.element {
/* 备选值中也可以使用 var() */
padding: calc(var(--spacing-base, 1rem) * 2);
}
空值 vs 未定义
/* 变量未定义 */
.element {
color: var(--undefined-color); /* 使用未定义的变量,效果等同于 auto/default */
}
/* 变量被设为空字符串 */
.element {
--undefined-color: ; /* 空字符串是有效值 */
color: var(--undefined-color, red); /* 会被解析 */
}
层叠与继承
自定义属性的层叠
/* 在 :root 定义 */
:root {
--color: blue;
}
/* 在具体选择器中覆盖 */
.special {
--color: red;
}
.normal {
color: var(--color); /* 继承自 :root,blue */
}
.special {
color: var(--color); /* 被覆盖,red */
}
自定义属性的继承
.parent {
--text-color: black;
}
.child {
color: var(--text-color); /* 继承父元素的 --text-color */
}
/* 没有定义 --text-color 的元素 */
.other {
color: var(--text-color, gray); /* 回退到备选值 gray */
}
computed value 计算值
:root {
--base-size: 16px;
}
.element {
/* calc() 中使用变量 */
font-size: calc(var(--base-size) * 1.5); /* 24px */
/* 运算中的单位处理 */
margin: calc(var(--base-size) * 2); /* 32px */
padding: calc(var(--base-size) + 8px); /* 24px */
}
响应式设计中的应用
断点变量
:root {
--container-width: 100%;
}
@media (min-width: 768px) {
:root {
--container-width: 720px;
}
}
@media (min-width: 1024px) {
:root {
--container-width: 960px;
}
}
.container {
width: var(--container-width);
margin: 0 auto;
}
主题切换
/* 亮色主题 */
:root {
--bg-primary: #ffffff;
--text-primary: #1a1a1a;
--bg-secondary: #f5f5f5;
}
/* 暗色主题 */
[data-theme="dark"] {
--bg-primary: #1a1a1a;
--text-primary: #ffffff;
--bg-secondary: #2d2d2d;
}
.page {
background: var(--bg-primary);
color: var(--text-primary);
}
组件变体
.button {
/* 基础样式 */
--button-bg: var(--color-primary);
--button-text: white;
--button-padding: 0.75rem 1.5rem;
background: var(--button-bg);
color: var(--button-text);
padding: var(--button-padding);
}
/* 变体覆盖变量 */
.button--outline {
--button-bg: transparent;
--button-text: var(--color-primary);
border: 1px solid var(--color-primary);
}
.button--large {
--button-padding: 1rem 2rem;
font-size: 1.125rem;
}
高级用法
条件变量(@supports)
@supports (color: oklch(0 0 0)) {
:root {
--color-primary: oklch(60% 0.2 250);
}
}
@supports not (color: oklch(0 0 0)) {
:root {
--color-primary: #3b82f6;
}
}
动态属性名
.element {
--side: top;
/* 动态构建属性名 */
border-: var(--side): 1px solid red; /* border-top: 1px solid red */
}
与 JavaScript 交互
:root {
--progress: 0;
}
.progress-bar {
width: calc(var(--progress) * 1%);
}
// JavaScript 中修改 CSS 变量
const root = document.documentElement;
root.style.setProperty('--progress', 75);
CSS 变量 vs 预处理器变量
| 特性 | CSS 变量 | Sass $变量 |
|---|---|---|
| 运行时可用 | 是 | 否(编译时) |
| 可继承 | 是 | 否 |
| 可在媒体查询中修改 | 是 | 否 |
| 可被 JavaScript 修改 | 是 | 否 |
| 可用 var() 动态引用 | 是 | 否 |
/* Sass:编译时替换 */
$color-primary: blue;
.button { color: $color-primary; }
/* 编译后:.button { color: blue; } */
/* CSS 变量:运行时替换 */
:root { --color-primary: blue; }
.button { color: var(--color-primary); }
/* 浏览器中仍然使用 var(),运行时解析 */
实战:设计系统变量结构
/* 1. 设计 Token */
:root {
/* 颜色 */
--color-brand-50: #eff6ff;
--color-brand-100: #dbeafe;
--color-brand-500: #3b82f6;
--color-brand-900: #1e3a8a;
/* 间距 */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 1rem;
--space-4: 1.5rem;
--space-6: 3rem;
/* 圆角 */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
--radius-full: 9999px;
}
/* 2. 语义变量 */
:root {
--color-bg: var(--color-brand-50);
--color-text: var(--color-brand-900);
--color-accent: var(--color-brand-500);
}
/* 3. 组件变量 */
.button {
--button-bg: var(--color-accent);
--button-text: white;
--button-radius: var(--radius-md);
--button-padding: var(--space-2) var(--space-4);
background: var(--button-bg);
color: var(--button-text);
padding: var(--button-padding);
border-radius: var(--button-radius);
}
面试高频问题
Q: CSS 变量和 Sass 变量有什么区别?
回答要点:CSS 变量是运行时解析的,可继承、可通过 JavaScript 修改、可以在媒体查询中动态改变;Sass 变量是编译时替换的,不具备这些运行时能力。CSS 变量适合做主题系统和响应式设计,Sass 变量适合存储静态配置值。
Q: var() 的备选值机制是怎样的?
回答要点:var() 的第二个参数是备选值,当变量未定义或被设为无效值时使用。备选值可以是任何有效的 CSS 值,也可以是另一个 var()。这支持链式回退:var(--color, var(--fallback, red))。
Q: 如何用 CSS 变量实现主题切换?
回答要点:在 :root 或 html 上定义颜色变量,在 [data-theme="dark"] 等选择器中覆盖这些变量。JavaScript 只需要修改 document.documentElement 的 data-theme 属性即可切换主题,整个页面的颜色自动响应。