如何在CSS中使用变量
CSS变量(正式名称为自定义属性)是用户定义的值,可以在你的代码库中设置一次并多次使用。它们使管理颜色、字体、大小和动画值变得更加容易,并确保在整个网络应用中的一致性。
例如,你可以将品牌颜色设置为一个CSS属性(--primarycolor: #7232FA ),并在任何使用你的品牌颜色的组件或样式中使用这个值(background: var(--primarycolor); )。
除了提供更干净和不重复的代码,CSS变量还可以用来建立调色板,提高响应速度,以及创建动态类型系统。
这篇文章摘自我的指南《CSS大师》,它教你写出更好、更高效的CSS。你还将学习掌握一些工具,这些工具将改善你的工作流程,建立更好的应用程序。
定义一个CSS变量
要定义一个自定义属性,请选择一个名称,并在其前面加上两个连字符。任何字母数字字符都可以成为名称的一部分。连字符 (-) 和下划线 (_) 字符也是允许的。广泛的Unicode字符可以成为自定义属性名称的一部分。这包括表情符号,但为了清晰和可读性,坚持使用字母数字名称。
这里有一个例子:
--primarycolor: #0ad0f9ff; /* RGB alpha hexadecimal color notation */
-- ,向CSS解析器表明这是一个自定义属性。当作为一个变量使用时,解析引擎会将该属性替换为其值。
自定义属性名称是区分大小写的。这意味着--primaryColor 和--primarycolor 被认为是两个不同的属性名称。这与传统的CSS有所不同,在传统的CSS中,属性和值的大小写并不重要。然而,它与ECMAScript中的变量名规则是一致的。
与其他属性一样,如display 或font ,CSS自定义属性必须在声明块中定义。一个常见的模式是使用:root 伪元素作为选择器来定义自定义属性:
:root {
--primarycolor: #0ad0f9ff;
}
:root 是一个指向文档根元素的伪元素。对于HTML文档来说,这就是 元素。对于SVG文档,它是 元素。使用 ,可以使属性在整个文档中立即可用。<html> <svg> :root
使用一个CSS变量
要将一个自定义属性作为变量使用,我们需要使用var() 函数。例如,如果我们想使用我们的--primarycolor 自定义属性作为背景颜色,我们要做的是如下工作:
body {
background-color: var(--primarycolor);
}
我们的自定义属性的值将成为background-color 属性的计算值。
到目前为止,自定义属性只能作为变量来为标准的CSS属性设置值。例如,你不能将一个属性名称存储为一个变量,然后重新使用它。下面的CSS就不能用了:
:root {
--top-border: border-top; /* Can't set a property as custom property's value */
var(--top-border): 10px solid #bc84d8; /* Can't use a variable as a property */
}
你也不能把一个属性-值对存储为一个变量,然后重新使用它。下面的例子也是无效的:
:root {
--text-color: 'color: orange'; /* Invalid property value */
}
body {
var(--text-color); /* Invalid use of a property */
}
最后,你不能把一个变量作为值字符串的一部分进行连接:
:root {
--base-font-size: 10;
}
body {
font: var(--base-font-size)px / 1.25 sans-serif; /* Invalid CSS syntax */
}
CSS自定义属性vs.CSS变量
"自定义属性 "是一个面向未来的名称,它考虑到了这个功能将来可能被使用。然而,如果浏览器供应商实施了CSS扩展规范,这可能会发生变化。该规范定义了用自定义选择器组合、函数和at-rules来扩展CSS的方法。
我们通常称自定义属性为 "变量",到目前为止,这是我们可以使用它们的唯一方式。在理论上,它们并不是完全可以互换的术语。在实践中和现在,它们是。在这篇文章中,我主要使用自定义属性,因为那是它们的正确名称。如果能让句子更清晰,我就会使用变量。
设置一个回退值
var() 函数最多接受两个参数。第一个参数应该是一个自定义属性名称。第二个参数是可选的,但必须是一个声明值。这个声明值的作用是当自定义属性值没有被定义时,作为一个回退或默认值来应用。
让我们来看看下面这个CSS:
.btn__call-to-action {
background: var(--accent-color, deepskyblue);
}
如果定义了--accent-color --比如说它的值是#f30 --那么任何带有.btn__call-to-action 类属性的路径的填充颜色将是橘红色的。如果它没有被定义,那么填充将是深天蓝色。
声明值也可以是嵌套的。换句话说,你可以使用一个变量作为var 函数的回退值:
body {
background-color: var(--books-bg, var(--arts-bg));
}
在上面的CSS中,如果定义了--books-bg ,背景色将被设置为--books-bg 属性的值。如果没有,背景色将是分配给--arts-bg 的任何值。如果这两个都没有定义,背景色将是该属性的初始值--本例是transparent 。
当一个自定义属性的值对它所使用的属性来说是无效的,也会发生类似的情况。考虑一下下面的CSS:
:root {
--text-primary: #600;
--footer-link-hover: #0cg; /* Not a valid color value */
}
body {
color: var(--text-primary);
}
a:link {
color: blue;
}
a:hover {
color: red;
}
footer a:hover {
color: var(--footer-link-hover);
}
在这种情况下,--footer-link-hover 属性的值不是一个有效的颜色。相反,footer a:hover 从<body> 元素中继承了它的颜色。
自定义属性的解决方式与其他CSS值的解决方式相同。如果值是无效的或未定义的,如果该属性是可继承的(如color 或font ),CSS解析器将使用继承的值,如果不是,则使用初始值(如background-color )。
级联值
自定义属性也遵守级联的规则。它们的值可以被后续的规则所覆盖:
:root {
--text-color: #190736; /* navy */
}
body {
--text-color: #333; /* dark gray */
}
body {
color: var(--text-color);
}
在上面的例子中,我们的正文文本将是深灰色。我们还可以在每个选择器的基础上重置值。让我们为这个CSS再添加几条规则:
:root {
--text-color: #190736; /* navy */
}
body {
--text-color: #333; /* dark gray */
}
p {
--text-color: #f60; /* orange */
}
body {
color: var(--text-color);
}
p {
color: var(--text-color)
}
在这种情况下,任何被<p> 元素标签包裹的文本将是橙色的。但是,<div> 或其他元素中的文本仍将是深灰色。
你也可以使用style 属性来设置自定义属性的值--例如,style="--brand-color: #9a09af" 。
自定义属性和调色板
自定义属性在管理HSL调色板方面效果特别好。HSL是指色调、饱和度、亮度。它是一种基于光的颜色模型,类似于RGB。我们可以在CSS中使用HSL值,这要感谢hsl() 和hsla() 颜色函数。hsl() 函数接受三个参数:色调、饱和度和明度。hlsa() 函数还接受第四个参数,表示颜色的阿尔法透明度(数值在0和1之间)。
RGB系统用红、绿、蓝的比例来表达颜色,而HSL使用一个色相圆,色相是该圆上的一个度数位置,而色调或阴影是用饱和度和亮度值来定义的。饱和度的范围从0%到100%,其中0%是灰色,100%是全色。明度的范围也可以从0%到100%,其中0%是黑色,100%是白色,50%是正常颜色:

来自Openclipart的CrazyTerabyte的色轮。
在HSL色彩系统中,红、绿、蓝三原色分别位于0度/360度、120度和240度之间。二次色--青色、品红和黄色--也相距120度,但位于原色的对面,分别为180度、300度和60度/420度。三级、四级和其他颜色则以大约10度的增量介于两者之间。蓝色,用HSL符号书写,将是hsl(240, 100%, 50%) 。
HSL参数单位
当您为hsl() 和hsla() 函数的第一个参数使用无单位值时,浏览器会假定它是一个以度为单位的角度。但是,你可以使用任何支持的CSS角度单位。蓝色也可以表示为hsl(240deg, 100%, 50%) 、hsl(4.188rad, 100%, 50%) 或hsla(0.66turn, 100% 50%) 。
这就是有趣之处。我们可以使用一个自定义属性来设置我们的色调值,并通过调整饱和度和亮度值来设置较浅和较深的色调:
:root {
--brand-hue: 270deg; /* purple */
--brand-hue-alt: .25turn; /* green */
/*
hsl() and hsla() can accept comma-separated or space-separated arguments,
but older browsers (such as Internet Explorer 11) only support
comma-separated arguments.
*/
--brand-primary: hsl( var( --brand-hue ) 100% 50% );
--brand-highlight: hsl( var( --brand-hue ) 100% 75% );
--brand-lowlight: hsl( var( --brand-hue ) 100% 25% );
--brand-inactive: hsl( var( --brand-hue ) 50% 50% );
--brand-secondary: hsl( var( --brand-hue-alt ) 100% 50% );
--brand-2nd-highlight: hsl( var( --brand-hue-alt ) 100% 75% );
--brand-2nd-lowlight: hsl( var( --brand-hue-alt ) 100% 25% );
--brand-2nd-inactive: hsl( var( --brand-hue-alt ) 50% 50% );
}
上面的CSS为我们提供了下图所示的调色板:

这是一个简单的版本,但你也可以使用自定义属性来调整饱和度和明度值。
强大的调色板生成
另一个想法是结合自定义属性和calc() 函数,从基本色调中生成一个方形的配色方案。让我们在下一个例子中创建一个方形配色方案。正方形配色方案由四种颜色组成,这些颜色在色轮上是等距的,也就是相距90度:
:root {
--base-hue: 310deg; /* Hot pink */
--distance: 90deg;
--color-a: hsl( var(--base-hue), 100%, 50% );
--color-b: hsl( calc( var(--base-hue) + var( --distance ) ), 100%, 50% );
--color-c: hsl( calc( var(--base-hue) + ( var( --distance ) * 2 ) ), 100%, 50% );
--color-d: hsl( calc( var(--base-hue) + ( var( --distance ) * 3 ) ), 100%, 50% );
}
这一点CSS为我们提供了如下所示的颇具热带风情的色彩方案:

自定义属性也能与媒体查询很好地配合,我们将在后面的章节中看到这一点。
使用CSS变量来制作黑暗主题调色板
你可以使用CSS自定义属性为网站的深色和浅色主题定义一组变量。
以下面这个页面的样式为例,我们可以在:root 中为相应的颜色定义自定义属性后,用变量替换不同选择器中的所有HSL颜色:
:root{
/*...*/
--nav-bg-color: hsl(var(--primarycolor) , 50%, 50%);
--nav-text-color: hsl(var(--primarycolor), 50%, 10%);
--container-bg-color: hsl(var(--primarycolor) , 50%, 95%);
--content-text-color: hsl(var(--primarycolor) , 50%, 50%);
--title-color: hsl(var(--primarycolor) , 50%, 20%);
--footer-bg-color: hsl(var(--primarycolor) , 93%, 88%);
--button-text-color: hsl(var(--primarycolor), 50%, 20%);
}
为自定义属性使用了适当的名称。例如,--nav-bg-color 是指导航背景的颜色,而--nav-text-color 是指导航前景/文本的颜色。
现在复制:root 选择器及其内容,但添加一个带有深色值的主题属性:
:root[theme='dark']{
/*...*/
}
如果在<html> 元素上添加了一个带深色值的主题属性,这个主题将被激活。
我们现在可以手动玩弄这些变量的值,通过减少HSL颜色的亮度值来提供一个黑暗的主题,或者我们可以使用其他技术,如CSS过滤器,如invert() 和brightness() ,它们通常用于调整图像的渲染,但也可用于任何其他元素。
将以下代码添加到:root[theme='dark'] :
:root[theme='dark'] {
--dark-hue: 240;
--light-hue: 250;
--primarycolor: var(--dark-hue);
--nav-bg-color: hsl(var(--primarycolor), 50%, 90%);
--nav-text-color: hsl(var(--primarycolor), 50%, 10%);
--container-bg-color: hsl(var(--primarycolor), 50%, 95%);
--content-text-color: hsl(var(--primarycolor), 50%, 50%);
--title-color: hsl(--primarycolor, 50%, 20%);
--footer-bg-color: hsl(var(--primarycolor), 93%, 88%);
--button-text-color: hsl(var(--primarycolor), 50%, 20%);
filter: invert(1) brightness(0.6);
}
invert() 过滤器颠倒了所选元素中的所有颜色(本例中为每个元素)。反转的值可以用百分比或数字来指定。100% 或1 的值将完全颠倒该元素的色调、饱和度和亮度值。
brightness() 滤波器使一个元素更亮或更暗。值为0 的结果是一个完全黑暗的元素。
invert() 滤波器使一些元素非常明亮。这些元素可以通过设置brightness(0.6) 来调和。
一个黑暗的主题有不同程度的黑暗:

用JavaScript切换主题
现在让我们用JavaScript在用户点击深色/浅色按钮时切换深色和浅色主题。在你的HTML中,在关闭的</body> 之前添加一个内联<script> ,代码如下:
const toggleBtn = document.querySelector("#toggle-theme");
toggleBtn.addEventListener('click', e => {
console.log("Switching theme");
if(document.documentElement.hasAttribute('theme')){
document.documentElement.removeAttribute('theme');
}
else{
document.documentElement.setAttribute('theme', 'dark');
}
});
Document.documentElement指的是文档的根DOM元素--也就是<html> 。这段代码使用.hasAttribute() 方法检查主题属性是否存在,如果不存在,则添加带有暗色值的属性,导致切换到暗色主题。否则,它将删除该属性,从而导致切换到浅色主题。
注意: 你还应该把它与CSS中的prefers-color-scheme功能结合起来使用,它可以用来从用户的操作系统或用户代理(浏览器)设置中自动改变浅色/深色主题。这将在下一节中展示。
使用自定义属性和媒体查询
我们还可以用媒体查询来使用自定义属性。例如,你可以使用自定义属性来定义浅色和深色方案:
:root {
--background-primary: hsl(34, 78%, 91%);
--text-primary: hsl(25, 76%, 10%);
--button-primary-bg: hsl(214, 77%, 10%);
--button-primary-fg: hsl(214, 77%, 98%);
}
@media screen and ( prefers-color-scheme: dark ) {
:root {
--background-primary: hsl(25, 76%, 10%);
--text-primary: hsl(34, 78%, 91%);
--button-primary-bg: hsl(214, 77%, 98%);
--button-primary-fg: hsl(214, 77%, 10%);
}
}
同样地,我们可以使用自定义属性来改变屏幕与打印的基本字体大小:
:r:root {
--base-font-size: 10px;
}
@media print {
:root {
--base-font-size: 10pt;
}
}
html {
font: var(--base-font-size) / 1.5 sans-serif;
}
body {
font-size: 1.6rem;
}
在这种情况下,我们要为打印和屏幕使用适合的媒体单位。对于这两种媒体,我们将使用10个单位的基本字体大小--屏幕为像素,印刷为点。我们还将使用--base-font-size: 的值来为我们的根元素设置一个起始尺寸(html)。然后我们可以使用rem 单位来确定我们的排版相对于基本字体大小的大小。
用JavaScript使用自定义属性
记住:自定义属性是CSS属性,我们可以像这样与它们互动。例如,我们可以使用CSS.supports() API来测试一个浏览器是否支持自定义属性:
const supportsCustomProps = CSS.supports('--primary-text: #000');
// Logs true to the console in browsers that support custom properties
console.log(supportsCustomProps);
我们还可以使用setProperty() 方法来设置一个自定义属性的值:
document.body.style.setProperty('--bg-home', 'whitesmoke');
使用removeProperty() ,也有类似的作用。只需将自定义属性的名称作为参数传递:
document.body.style.removeProperty('--bg-home');
要用JavaScript将自定义属性作为一个值,使用var() 函数,将属性名称作为参数:
document.body.style.backgroundColor = 'var(--bg-home)';
唉,你不能使用方括号语法或样式对象的camelCased属性来设置自定义属性。换句话说,document.body.style.--bg-home 和document.body.style['--bg-home'] 都不起作用。
自定义属性和组件
像React、Angular和Vue这样的JavaScript框架让开发者使用JavaScript来创建可重复使用、可共享的HTML块,通常在组件级别定义CSS。
这里有一个React组件的例子,是用**JSX**编写的,这是一种JavaScript的语法扩展:
import React from 'react';
/* Importing the associated CSS into this component */
import '../css/field-button.css';
class FieldButtonGroup extends React.Component {
render() {
return (
<div className="field__button__group">
<label htmlFor={this.props.id}>{this.props.labelText}</label>
<div>
<input type={this.props.type}
name={this.props.name}
id={this.props.id}
onChange={this.props.onChangeHandler} />
<button type="submit">{this.props.buttonText}</button>
</div>
</div>
);
}
}
export default FieldButtonGroup;
更多关于JavaScript框架的信息
我们的React组件将CSS导入到一个JavaScript文件中。编译时,field-button.css 的内容被内联加载。这里有一种可能的方法,可以用自定义属性来使用:
.field__button__group label {
display: block;
}
.field__button__group button {
flex: 0 1 10rem;
background-color: var(--button-bg-color, rgb(103, 58, 183)); /* include a default */
color: #fff;
border: none;
}
在这个例子中,我们使用了一个自定义属性----button-bg-color --作为按钮的背景颜色,同时还有一个默认颜色,以防--button-bg-color 没有被定义。从这里,我们可以在全局样式表中或通过style 属性在本地设置一个--button-bg-color 的值。
让我们把这个值设置为React "prop"。React道具(Properties的缩写)模仿了元素属性。它们是一种将数据传入React组件的方式。在这种情况下,我们将添加一个名为buttonBgColor 的道具:
import FieldButtonGroup from '../FieldButtonGroup';
class NewsletterSignup extends React.Component {
render() {
// For brevity, we've left out the onChangeHandler prop.
return (
<FieldButtonGroup type="email" name="newsletter" id="newsletter"
labelText="E-mail address" buttonText="Subscribe"
buttonBgColor="rgb(75, 97, 108)" />
);
}
}
export default NewsletterSignup;
现在我们需要更新我们的FieldButtonGroup ,以支持这一变化:
class FieldButtonGroup extends React.Component {
render() {
/*
In React, the style attribute value must be set using a JavaScript
object in which the object keys are CSS properties. Properties
should either be camelCased (e.g. backgroundColor) or enclosed in
quotes.
*/
const buttonStyle = {
'--button-bg-color': this.props.buttonBgColor
};
return (
<div className="field__button__group">
<label htmlFor={this.props.id}>{this.props.labelText}</label>
<div>
<input type={this.props.type}
name={this.props.name} id={this.props.id}
onChange={this.props.onChangeHandler} />
<button type="submit" style={buttonStyle}>
{this.props.buttonText}
</button>
</div>
</div>
);
}
}
在上面的代码中,我们已经添加了一个buttonStyle 对象,它持有我们的自定义属性的名称,并将其值设置为我们的buttonBgColor 道具的值,以及我们的按钮的style 属性。
使用style 属性可能与你所学到的关于编写CSS的所有知识相悖。CSS的一个卖点是,我们可以定义一套样式,在多个HTML和XML文档中使用。另一方面,style 属性将该CSS的范围限制在它所应用的元素上。我们不能重复使用它。我们也不能利用级联的优势。
但是,在一个基于组件的前端架构中,一个组件可能会在多种情况下被多个团队使用,甚至可能在客户项目中共享。在这些情况下,你可能想把级联的 "全局范围 "与style 属性所提供的狭窄的 "局部范围 "结合起来。
用style 属性设置自定义属性值,将效果限制在FieldButtonGroup 组件的这个特定实例中。但由于我们使用了自定义属性而不是标准的CSS属性,我们仍然可以选择在链接的样式表中定义--button-bg-color ,而不是作为一个组件道具。
总结
自定义属性采用了预处理程序的一个最好的特性--变量,并使其成为CSS的原生属性。使用自定义属性,我们可以:
- 创建可重复使用的、有主题的组件
- 为各种视口尺寸和媒体轻松调整填充、边距和排版
- 提高我们的CSS中颜色值的一致性