如何在CSS中使用变量(详细教程)

228 阅读16分钟

如何在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中的变量名规则是一致的。

与其他属性一样,如displayfont ,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值的解决方式相同。如果值是无效的或未定义的,如果该属性是可继承的(如colorfont ),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%是正常颜色:

An HSL color wheel

来自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为我们提供了下图所示的调色板:

Using custom properties with the HSL function to generate a color palette

这是一个简单的版本,但你也可以使用自定义属性来调整饱和度和明度值。

强大的调色板生成

另一个想法是结合自定义属性和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为我们提供了如下所示的颇具热带风情的色彩方案:

Generating a square color scheme from a base hue using an HSL function to generate a color palette

自定义属性也能与媒体查询很好地配合,我们将在后面的章节中看到这一点。

使用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) 来调和。

一个黑暗的主题有不同程度的黑暗:

A dark theme

用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-homedocument.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中颜色值的一致性