技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTCSS

CSS 自定义属性(CSS 变量)

所属馆藏
CSS
文件格式
Markdown
原始路径
CSS/03-css 变量
本文目录14 个章节
  1. 一、什么是 CSS 自定义属性?
  2. 二、为什么经常在 :root 中定义变量?
  3. 三、局部定义和覆盖
  4. 四、继承、层叠和计算
  5. 五、var() 函数
  6. 六、自定义属性值的类型和单位
  7. 七、响应式布局中的 CSS 变量
  8. 八、兼容性处理
  9. 九、使用 JavaScript 动态更新 CSS 变量
  10. 十、使用 @property 注册类型
  11. 十一、设计令牌和主题实践
  12. 十二、常见误区
  13. 十三、图片和参考资料
  14. 总结

CSS 自定义属性(CSS 变量)

Category(分类): CSS Status: 已整理

CSS 自定义属性通常被称为 CSS 变量。它们可以集中管理颜色、尺寸、间距、动画时长和主题配置,也可以在运行时通过 JavaScript 动态更新。

CSS 自定义属性不是 Sass 或 Less 那种编译阶段变量,而是浏览器运行时参与层叠、继承和计算的 CSS 属性。

一、什么是 CSS 自定义属性?

自定义属性名必须以两个连字符 -- 开头:

:root {
  --main-color: hsl(240 10% 3.9%);
  --secondary-color: hsl(0 0% 98%);
}

body {
  background-color: var(--main-color);
  color: var(--secondary-color);
}

在上例中,--main-color--secondary-color 是自定义属性,通过 var() 函数读取。

有效的自定义属性名可以包含字母、数字、连字符和下划线等字符,例如:

:root {
  --example-name: red;
  --example--name: blue;
}

自定义属性名区分大小写:

:root {
  --header-color: #333;
  --Header-Color: #666;
}

上面是两个不同的自定义属性。

自定义属性和 Sass 变量的区别

// Sass 变量:构建阶段被替换
$main-color: #06c;

// CSS 自定义属性:保留在输出 CSS 中,运行时计算
:root {
  --main-color: #06c;
}

Sass 变量在编译后通常不会保留;CSS 自定义属性会保留在浏览器中,因此可以:

  • 根据媒体查询改变值;
  • 根据组件作用域覆盖值;
  • 通过 JavaScript 动态修改;
  • 参与运行时计算和动画。

二、为什么经常在 :root 中定义变量?

:root 是一个伪类,在 HTML 文档中通常匹配根元素 <html>

:root {
  --global-bg-color: hsl(240 10% 3.9%);
  --global-text-color: hsl(0 0% 98%);
  --header-height: 4rem;
  --content-padding: 1rem 1.5rem;
  --transition-duration: 0.35s;
}

由于自定义属性默认具有继承性,根元素上的变量通常可以被后代元素读取,因此适合放置全局设计令牌。

但“全局变量”只是便于理解的说法,它并不是编程语言中的全局变量:

  • 后代元素可以覆盖它;
  • 变量只沿继承链传递;
  • 独立的元素树无法读取另一个元素的局部变量;
  • Shadow DOM、iframe 等边界需要单独考虑;
  • !important 和层叠规则仍然会影响最终值。

三、局部定义和覆盖

自定义属性可以定义在任意选择器中:

.section {
  --section-bg-color: hsl(200 15% 40%);
  background-color: var(--section-bg-color);
}

--section-bg-color 通常可以被 .section 及其后代读取,其他不在该继承范围内的元素不能直接读取它。

局部主题示例

<div class="container">
  <p>默认主题文本</p>
  <button class="button">局部主题按钮</button>
</div>

<button class="button">外部按钮</button>
:root {
  --text-color: #111827;
}

.container {
  --text-color: #fff;
  padding: 1rem;
  background: #1d4ed8;
}

.button {
  color: var(--text-color);
}

只有 .container 内的按钮会继承白色 --text-color。外部按钮仍然使用 :root 中的深色值。

四、继承、层叠和计算

自定义属性的最终值需要结合 CSS 层叠和继承理解。浏览器会针对每个元素计算变量值,而不是在整个文档中选择一个统一的“最高优先级变量”。

<p>根元素颜色</p>
<div>div 颜色</div>
<div id="alert">警告颜色</div>
:root {
  --color: blue;
}

div {
  --color: green;
}

#alert {
  --color: red;
}

* {
  color: var(--color);
}

结果通常是:

  • <p> 从根元素继承 blue
  • 普通 <div> 使用自身的 green
  • #alert 使用自身层叠后的 red

自定义属性本身也遵循层叠规则:如果同一个元素上有多条匹配的声明,会根据来源、重要性、层叠层、优先级和声明顺序决定最终值。

五、var() 函数

var() 用于读取自定义属性:

.link {
  color: var(--main-color);
  text-decoration-color: var(--secondary-color);
}

使用回退值

var() 的第二个参数是回退值:

.card {
  color: var(--text-color, #333);
}

--text-color 未定义、无效或形成循环时,浏览器可以使用 #333 作为回退值。

回退值中的逗号和空格会被视为回退内容的一部分:

:root {
  --font-stack: "Roboto", "Helvetica", sans-serif;
  --card-padding: 10px 15px 20px;
}

.text {
  font-family: var(--font-stack);
  padding: var(--card-padding);
}

如果变量存在,但替换后的结果不符合目标属性语法,目标声明仍可能在计算值阶段失效,不能把回退值理解为对所有错误值的自动修复。

在变量定义中引用变量

:root {
  --primary-color: red;
  --logo-text-color: var(--primary-color);
}

如果自定义属性互相引用形成循环,相关变量会变成无效值:

:root {
  --a: var(--b);
  --b: var(--a);
}

不能把变量用作属性名

.foo {
  --side: margin-top;

  /* 无效:var() 不能替换属性名 */
  var(--side): 20px;
}

自定义属性也不能直接用作选择器名称、媒体查询名称或任意 CSS 语法结构的替代品。它们主要用于属性值和部分允许计算值的场景。

六、自定义属性值的类型和单位

默认情况下,自定义属性保存的是 CSS token 序列,浏览器不会像 Sass 那样提前把它识别成一个固定类型。

数字和单位

数字和单位不能直接拼接:

.foo {
  --gap: 20;

  /* 无效或无法按预期解析 */
  margin-top: var(--gap)px;
}

应使用 calc()

.foo {
  --gap: 20;
  margin-top: calc(var(--gap) * 1px);
}

也可以直接把单位放在变量值中:

.foo {
  --gap: 20px;
  margin-top: var(--gap);
}

字符串值

如果变量值带引号,它通常是字符串 token,不能直接当作长度使用:

/* 无效:字符串不是长度 */
.foo {
  --size: '20px';
  font-size: var(--size);
}

/* 有效 */
.bar {
  --size: 20px;
  font-size: var(--size);
}

content 等允许字符串的属性中,可以组合字符串和变量:

:root {
  --label: "external link";
}

.link::after {
  content: " [" var(--label) "]";
}

这不是通用的字符串拼接,而是变量替换后由目标 CSS 属性重新解析。

保存给 JavaScript 的数据

自定义属性可以保存浏览器不理解的 token,JavaScript 可以读取这些 token:

:root {
  --js-message: if(x > 5) this.width = 10;
}
const message = getComputedStyle(document.documentElement)
  .getPropertyValue('--js-message')
  .trim()

console.log(message)

这些内容不会被 CSS 执行,也不会自动变成 JavaScript。它只是 CSS 与 JavaScript 之间传递字符串数据的一种方式,实际项目应避免把不可信内容当作代码执行。

七、响应式布局中的 CSS 变量

CSS 自定义属性可以在媒体查询中重新定义:

:root {
  --primary-color: #7f583f;
  --secondary-color: #f7efd2;
  --content-padding: 1rem;
}

.page-link {
  color: var(--primary-color);
  text-decoration-color: var(--secondary-color);
  padding-inline: var(--content-padding);
}

@media (min-width: 768px) {
  :root {
    --primary-color: #f7efd2;
    --secondary-color: #7f583f;
    --content-padding: 2rem;
  }
}

这样可以集中改变主题值,而不用重复修改多个选择器。

也可以在组件局部响应式覆盖:

.card {
  --card-padding: 1rem;
  padding: var(--card-padding);
}

@media (min-width: 768px) {
  .card {
    --card-padding: 1.5rem;
  }
}

八、兼容性处理

现代主流浏览器已经广泛支持 CSS 自定义属性。需要支持不支持自定义属性的旧浏览器时,可以提供普通 CSS 回退:

.link {
  color: #7f583f;
  color: var(--primary-color);
}

不支持 var() 的浏览器会忽略第二条声明,继续使用第一条。

也可以使用 @supports 检测:

@supports (--custom-property: 0) {
  .box {
    color: var(--primary-color);
  }
}

@supports not (--custom-property: 0) {
  .box {
    color: #7f583f;
  }
}

@supports 只能检测浏览器是否理解某种语法,不能保证具体变量值在所有场景下都符合目标属性要求。

九、使用 JavaScript 动态更新 CSS 变量

1. 检测支持情况

const isSupported =
  typeof CSS !== 'undefined' &&
  CSS.supports('--custom-property: 0')

if (isSupported) {
  console.log('支持 CSS 自定义属性')
} else {
  console.log('不支持 CSS 自定义属性')
}

2. 设置、读取和删除变量

主题变量通常设置在根元素上:

const root = document.documentElement

// 设置变量
root.style.setProperty('--primary-color', '#7f583f')

// 读取根元素上的内联变量
const inlineValue = root.style
  .getPropertyValue('--primary-color')
  .trim()

// 删除变量
root.style.removeProperty('--primary-color')

如果要读取元素最终继承或计算得到的值,应使用 getComputedStyle()

const value = getComputedStyle(document.documentElement)
  .getPropertyValue('--primary-color')
  .trim()

element.style.getPropertyValue() 只读取该元素的内联样式,不会返回来自样式表或父元素继承的值。

3. 使用鼠标坐标驱动 CSS

const root = document.documentElement

document.addEventListener('pointermove', (event) => {
  root.style.setProperty('--mouse-x', `${event.clientX}px`)
  root.style.setProperty('--mouse-y', `${event.clientY}px`)
})

对应 CSS:

.pointer-effect {
  transform: translate(
    var(--mouse-x, 0px),
    var(--mouse-y, 0px)
  );
}

高频事件中应注意性能,必要时使用 requestAnimationFrame 节流,避免每次指针移动都触发大量样式计算。

十、使用 @property 注册类型

默认自定义属性通常按不可插值的 token 处理。如果需要让自定义属性平滑动画,可以使用 @property 注册语法、继承方式和初始值:

@property --progress {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
}

.progress {
  --progress: 0;
  width: calc(var(--progress) * 1%);
  animation: fill 1s ease-out forwards;
}

@keyframes fill {
  to {
    --progress: 100;
  }
}

@property 的支持范围和语法限制需要根据目标浏览器测试。对于不支持的环境,应准备静态样式或 JavaScript 回退。

十一、设计令牌和主题实践

在大型项目中,可以把自定义属性分成基础令牌、语义令牌和组件令牌:

:root {
  /* 基础令牌 */
  --blue-600: #2563eb;
  --gray-100: #f3f4f6;
  --gray-900: #111827;

  /* 语义令牌 */
  --color-primary: var(--blue-600);
  --color-surface: #fff;
  --color-text: var(--gray-900);
}

[data-theme='dark'] {
  --color-surface: #111827;
  --color-text: var(--gray-100);
}

.button {
  color: var(--color-surface);
  background: var(--color-primary);
}

这样切换主题时只需要修改语义令牌,而不必修改所有组件选择器。

十二、常见误区

误区一:CSS 自定义属性就是 Sass 变量

不是。Sass 变量在构建阶段替换,CSS 自定义属性在浏览器运行时参与层叠、继承和计算。

误区二:定义在 :root 就永远不能覆盖

不是。后代元素可以重新定义同名变量,!important 和层叠规则也会影响结果。

误区三:变量可以替换任何 CSS 内容

不是。变量主要用于允许自定义属性替换的值位置,不能直接替换属性名、选择器或任意语法结构。

误区四:变量值一定有明确类型

默认自定义属性保存的是 token 序列。需要类型检查和动画插值时,可以考虑 @property

误区五:动态更新变量不会影响性能

修改根元素上的变量可能导致大量后代重新计算样式。应尽量缩小变量作用域,并避免在高频事件中无节制地修改全局变量。

十三、图片和参考资料

CSS 自定义属性可以配合主题系统、响应式布局和 JavaScript 使用。原文中的 CSS 变量示意图已保存到本地:

CSS 自定义属性示意图

参考资料:

总结

  1. 使用 --name 定义 CSS 自定义属性,使用 var(--name) 读取;
  2. :root 适合定义全局设计令牌,但自定义属性仍然遵循继承和层叠;
  3. 局部变量只对自身及其后代可用,不能被无关元素读取;
  4. var() 的第二个参数是回退值,但不能修复所有替换后的无效结果;
  5. 自定义属性保存的是 CSS token 序列,不等同于普通字符串或 Sass 变量;
  6. 数字和单位应使用带单位变量或 calc() 组合;
  7. @supports 可以用于兼容性检测,JavaScript 可以通过 CSSOM 动态更新变量;
  8. 高频更新变量时要注意样式计算成本;
  9. 需要类型约束和连续动画时,可以使用 @property
  10. 使用设计令牌组织颜色、尺寸和主题,可以减少重复样式并提高维护性。
457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS