大多数编程语言都支持变量。但令人遗憾的是,CSS从一开始就缺乏对本地变量的支持。
你写CSS?那么没有变数给你。那么,除非你使用像Sass这样的预处理器。
像Sass这样的预处理器将变量的使用作为一个大附加组件来销售。一个尝试他们的理由。你知道吗?这是一个很好的理由。
那么网络正在快速发展。我很高兴地报告CSS现在最终支持变量。
虽然预处理器支持更多的功能,但添加CSS变量是一个不错的选择。这些使网络更加贴近未来。
在本指南中,我将向您展示变量如何在CSS中以原生方式工作,以及如何使用它们使您的生活更轻松。
你会学到什么
我将首先引导您了解CSS变量的基础知识。我相信任何理解CSS变量的体面尝试都必须从这里开始。
学习基础很酷。更酷的是应用这些基础来构建真实世界的应用程序。
因此,我将通过向您展示如何构建3个展示CSS变量及其易用性的项目来包装。以下是这3个项目的快速预览。
项目1:使用CSS变量创建组件变体
您今天可能已经在构建组件变体。无论您使用React,Angular还是Vue,CSS变量都会使此过程更简单。
查看Codepen上的项目。
项目2:CSS变量的主题样式
你可能会在某处看到这个。我将展示CSS变量如何轻松创建站点范围的主题样式。
查看Codepen上的项目。
项目3:建立CSS可变展位
这是最后的项目。不介意这个名字。我无法想出一个更好的名字。
请注意盒子的颜色是如何动态更新的,以及盒子容器如何在3D空间中旋转,因为范围输入已更改。
该项目展示了使用JavaScript更新CSS变量的便捷性以及您获得的反应性好东西。
这将会非常好玩!
花一些时间在Codepen上玩一下吧。
注意:本文假定您对CSS有很好的把握。如果你不熟悉CSS,或者想学习制作令人惊叹的UI,我建议参加我的高级CSS课程(付费课程包括85课程)。本文是该课程的摘录。</无耻插件>
为什么变量如此重要
如果您对预处理器或原生CSS中的变量不熟悉,请参阅变量非常重要的几个原因。
原因#1:更多可读代码
不用多说,你可以很快地知道可读性和可维护性变量如何构成任何代码库。
理由2:跨大型文档的易用性
如果您将所有常量保存在单独的文件中,则当您想对变量进行更改时,不必跳过数千行代码。
它变得非常容易。只要在一个地方改变它,瞧。
原因3:你可以更快地发现错别字
搜索试图发现错误的代码行是一种痛苦。如果错误是由于简单的错字造成的,那更令人讨厌。他们很难发现。变量的良好使用消除了这些麻烦。
为此,可读性和可维护性是巨大的胜利。
感谢CSS变量,现在我们也可以使用原生CSS。
定义CSS变量
让我从你可能已经熟悉的东西开始:JavaScript中的变量。
一个简单的JavaScript变量可以这样声明:
var amAwesome;
然后你可以像这样给它赋值:
amAwesome = "awesome string"
在CSS中,CSS变量是名称以两个破折号开头的任何“属性”。
/*can you spot the variable here? */
.block {
color: #8cacea;
--color: blue
}
范围CSS变量
还有一点需要注意。
请记住,在JavaScript中,变量有一个范围。他们可能有一个global
或一个local
范围。
CSS变量也是如此。
考虑下面的例子:
:root {
--main-color: red
}
该 :root
选择可以让您针对DOM中最高级的元素,或文档树。
因此,以这种方式声明的变量是全球范围的范围。
了解?
例1
假设你想创建一个CSS变量来存储主题站点的主要颜色。
你会怎么做呢?
- 您创建范围选择器。使用
:root
一个“全局”变量
:root {
}
2.定义变量
:root {
--primary-color: red
}
请记住,CSS变量是任何名称以两个破折号开头的“属性”,例如 --color
那很简单。
使用CSS变量
一旦变量被定义并赋值,您可以继续在属性值内使用它。
尽管如此,还是有点困难。
如果您来自预处理器的世界,您必须习惯于通过引用其名称来使用变量。例如:
$font-size: 20px
.test {
font-size: $font-size
}
有了原生的CSS变量,事情有点不同。您通过使用该var()
函数引用一个变量。
使用上面的例子,使用CSS变量会产生这样的结果:
:root {
--font-size: 20px
}
.test {
font-size: var(--font-size)
}
很不一样。
一旦你明白了,你会开始喜欢CSS变量 – 很多!
另一个重要的注意事项是,与Sass(或其他预处理器)中的变量不同 – 您可以在很多地方使用变量,并按照您的要求进行数学运算 – 您需要注意CSS变量。你将主要将它们设置为属性值。
/*this is wrong*/
.margin {
--side: margin-top;
var(--side): 20px;
}
你也无法做数学。你需要CSS calc()
功能。我们将继续讨论示例。
/*this is wrong */
.margin {
--space: 20px * 2;
font-size: var(--space); //not 40px
}
如果你必须做数学,然后使用calc()函数,如下所示:
.margin {
--space: calc(20px * 2);
font-size: var(--space); /*equals 40px*/
}
值得一提的属性
这里有一些值得一提的行为。
1.自定义属性是普通属性,所以它们可以在任何元素上声明。
在段落元素,节,旁边,根或伪元素上声明它们。他们会按预期工作。
2.使用正常的继承和级联规则解决CSS变量
考虑下面的代码块:
div {
--color: red;
}
div.test {
color: var(--color)
}
div.ew {
color: var(--color)
}
与正常变量一样,该--color
值将由div继承。
3. CSS变量可以使用@media
条件规则和其他条件规则
与其他属性一样,您可以更改@media
块或其他条件规则中的CSS变量的值。
例如,以下代码更改较大设备上变量的值。
:root {
--gutter: 10px
}
@media screen and (min-width: 768px) {
--gutter: 30px
}
4. CSS的变量可以用在HTML的style属性中。
您可以选择内联设置变量的值,并且它们仍然按预期工作。
<!--HTML-->
<html style="--color: red">
<!--CSS-->
body {
color: var(--color)
}
CSS变量区分大小写。小心这个。我为自己节省了压力,并以小写形式写入变量。你的里程可能不同。
/*these are two different variables*/
:root {
--color: blue;
--COLOR: red;
}
解决多个声明
与其他属性一样,使用标准级联解决多个声明。
我们来看一个例子:
/*define the variables*/
:root { --color: blue; }
div { --color: green; }
#alert { --color: red; }
/*use the variable */
* { color: var(--color); }
通过上面的变量声明,以下元素的颜色是什么?
<p>What's my color?</p>
<div>and me?</div>
<div id='alert'>
What's my color too?
<p>color?</p>
</div>
你能解释一下吗?
第一段将是blue
。没有--color
在p
选择器上设置直接定义,所以它继承了来自的值 :root
:root { --color: blue; }
第一个div
将是green
。这很清楚。有一个直接的变量定义设置div
div { --color: green; }
该div
用的ID alert
将不会是绿色的。这将是red
#alert { --color: red; }
该ID具有直接变量范围。因此,定义内的值将覆盖其他值。选择器#alert
更具体。
最后,p
在#alert
意志内…red
段落元素没有变量声明。您可能预期颜色是blue
由于:root
元素声明造成的 。
:root { --color: blue; }
和其他属性一样,CSS变量是继承的。该值是从父级继承的,#alert
#alert { --color: red; }
解决循环依赖性
循环依赖以下列方式发生:
- 当一个变量取决于它自己。也就是说,它使用了一个
var()
指它自己的东西。
:root {
--m: var(--m)
}
body {
margin: var(--m)
}
2.当两个或更多个变量互相引用时。
:root {
--one: calc(var(--two) + 10px);
--two: calc(var(--one) - 10px);
}
小心不要在代码中创建循环依赖关系。
什么发生无效变量?
语法错误被丢弃,但无效var()
替换默认为有问题的属性的初始值或继承值。
考虑以下:
:root { --color: 20px; }
p { background-color: red; }
p { background-color: var(--color); }
如预期的那样,--color
替换成var()
属性值,background-color: 20px
替换后无效。由于backgroud-color
不是可继承的属性,因此该值将默认为其initial
值transparent
。
请注意,如果您的书面材料backgroud-color: 20px
没有任何可变的替代品,那么特定的背景声明将是无效的。之前的声明将被使用。
构建单个令牌时要小心
如下所示设置属性的值时,将20px
解释为单个标记。
font-size: 20px
一个简单的方法是,价值20px
被视为一个单一的“实体”。
在使用CSS变量构建单个令牌时,您需要小心。
例如,请考虑以下代码块:
:root {
--size: 20
}
div {
font-size: var(--size)px /*WRONG*/
}
你可能已经预期了font-size
产出的价值20px
,但这是错误的。
浏览器将其解释为 20 px
注意后面的空格 20
因此,如果您必须创建单个令牌,请让变量表示整个令牌。例如,--size: 20px
或者使用该calc
函数,例如calc(var(--size) * 1px)
where --size
等于20
如果你还没有得到这个,别担心。我会在接下来的例子中更详细地解释它。
让我们建立东西!
现在这是我们一直在等待的文章的一部分。
我将通过构建一些有用的项目,引导您了解所讨论概念的实际应用。
让我们开始吧。
项目1:使用CSS变量创建组件变体
考虑你需要建立两个不同按钮的情况。相同的基本款式,只是有点不同。
在这种情况下,不同的性质是background-color
和border-color
变体的。
那么,你会如何做到这一点?
这是典型的解决方案。
创建一个基类,说 .btn
并添加变体类。这是一个示例标记:
<button class="btn">Hello</button>
<button class="btn red">Hello</button>
.btn
将包含按钮上的基础样式。例如:
.btn {
padding: 2rem 4rem;
border: 2px solid black;
background: transparent;
font-size: 0.6em;
border-radius: 2px;
}
/*on hover */
.btn:hover {
cursor: pointer;
background: black;
color: white;
}
那么,变体在哪里?
这里:
/* variations */
.btn.red {
border-color: red
}
.btn.red:hover {
background: red
}
你看到我们如何在这里和那里复制代码?这很好,但我们可以使用CSS变量更好。
第一步是什么?
用CSS变量代替不同的颜色,不要忘记为变量添加默认值!
.btn {
padding: 2rem 4rem;
border: 2px solid var(--color, black);
background: transparent;
font-size: 0.6em;
border-radius: 2px;
}
/*on hover*/
.btn:hover {
cursor: pointer;
background: var(--color, black);
color: white;
}
当你这样做时:你说的是,将背景设置为变量的值 。但是,如果该变量不存在,请使用默认值background: var(--color, black)
--color
black
这是您如何设置默认变量值。就像你在JavaScript或任何其他编程语言中做的一样。
这是好的一部分。
使用变体时,您只需提供CSS变量的新值,如下所示:
.btn.red {
--color: red
}
就这样。现在,当使用.red
该类时 ,浏览器会记录不同的--color
变量值,并立即更新按钮的外观。
如果您花费大量时间来构建可重用组件,这非常好。
这是一个并排比较:
哦,如果你有更多的变体,你只需要为自己节省很多额外的输入。
项目2:带CSS变量的主题站点
我相信你以前遇到过他们。主题网站给用户定制的感觉。就像他们在控制。
以下是我们将要构建的基本示例。
那么,CSS变量有多容易呢?
我们来看看。
在此之前,我想提一下这个例子非常重要。在这个例子中,我将介绍使用JavaScript更新CSS变量的概念。
很好玩!
你会爱上它的。
我们真的想要做什么。
CSS变量的美妙之处在于它们的反应性。只要它们被更新,任何属性都会被CSS变量的值更新。
从概念上讲,这里是一个图像,用于解释关于手头示例的过程。
所以,我们需要一些用于点击监听器的JavaScript。
对于这个简单的例子,整个页面文本的背景和颜色是基于CSS变量的。
当你点击上面的任何按钮时,他们将CSS变量设置为其他颜色。因此,页面的背景被更新。
嘿,这就是它的全部。
呃,还有一件事。
当我说CSS变量被设置为其他值时,该怎么做?
即使将它们设置为内联,CSS变量也会生效。使用JavaScript,我们得到了一个根文件,并且为CSS变量内联设置了新的值。
了解?
这是很多的谈话 – 让我们做真实的事情。
最初的标记
所需的初始标记是这样的:
<div class="theme">
<button value="dark">dark</button>
<button value="calm">calm</button>
<button value="light">light</button>
</div>
<article>
...
</article>
标记由.theme
父元素内的三个按钮 组成。为了保持简短,我已经截断了article
元素内的内容。在这个article
元素中是页面的内容。
设计页面的样式
该项目的成功始于页面的样式。诀窍很简单。
而不是仅仅设置background-color
和color
在石头上的页面,我们将根据变量设置。
这是我的意思。
body {
background-color: var(--bg, white);
color: var(--bg-text, black)
}
原因很明显。无论何时按下按钮,我们都将更改文档中两个变量的值。
在此更改后,页面的整体风格将会更新。十分简单。
所以,让我们继续处理来自JavaScript的更新。
进入JavaScript
我会继续并且吐出这个项目所需的所有JavaScript。
const root = document.documentElement
const themeBtns = document.querySelectorAll('.theme > button')
themeBtns.forEach((btn) => {
btn.addEventListener('click', handleThemeUpdate)
})
function handleThemeUpdate(e) {
switch(e.target.value) {
case 'dark':
root.style.setProperty('--bg', 'black')
root.style.setProperty('--bg-text', 'white')
break
case 'calm':
root.style.setProperty('--bg', '#B3E5FC')
root.style.setProperty('--bg-text', '#37474F')
break
case 'light':
root.style.setProperty('--bg', 'white')
root.style.setProperty('--bg-text', 'black')
break
}
}
不要让这吓倒你。这比你想象的要容易得多。
首先,保留对根元素的引用, const root = document.documentElement
这里的根元素是HTML
元素。你会明白为什么这一点很重要。如果您好奇,则需要设置CSS变量的新值。
另外,请保留对按钮的引用, const themeBtns = document.querySelectorAll('.theme > button')
querySelectorAll
产生一个我们可以循环的类似数组的数据结构。迭代每个按钮并添加一个事件监听器,点击。
就是这样:
themeBtns.forEach((btn) => {
btn.addEventListener('click', handleThemeUpdate)
})
handleThemeUpdate
功能在哪里?接下来我会讨论这个问题。
每个被点击的按钮都会有handleThemeUpdate
其回调函数。注意点击了哪个按钮然后执行正确的操作变得非常重要。
鉴于此,使用开关operator
,并且基于被点击的按钮的值执行一些操作。
继续,再看看JavaScript代码块。你现在会明白好多了。
项目3:建立CSS可变展位
如果你错过了,我们将构建:
请记住盒子的颜色是动态更新的,并且盒子容器在三维空间中旋转,因为范围输入已更改。
你可以继续在Codepen上使用它。
这是使用JavaScript更新CSS变量以及随附的反应性的绝佳示例。
让我们看看如何建立这个。
标记
这里是所需的组件。
- 范围输入
- 容纳说明的容器
- 保存其他框的列表的部分,每个框都包含输入字段
标记变得简单。
这里是:
<main class="booth">
<aside class="slider">
<label>Move this </label>
<input class="booth-slider" type="range" min="-50" max="50" value="-50" step="5"/>
</aside>
<section class="color-boxes">
<div class="color-box" id="1"><input value="red"/></div>
<div class="color-box" id="2"><input/></div>
<div class="color-box" id="3"><input/></div>
<div class="color-box" id="4"><input/></div>
<div class="color-box" id="5"><input/></div>
<div class="color-box" id="6"><input/></div>
</section>
<footer class="instructions">
Move the slider<br/>
Write any color in the red boxes
</footer>
</main>
这里有几点需要注意。
- 范围输入表示从步进值
-50
到50
步进值的值。5
另外,范围输入的值是最小值,-50
- 如果您不确定范围输入的工作方式,请在w3schools上查看
- 请注意类的部分如何
.color-boxes
包含其他.color-box
容器。在这些容器内存在输入字段。 - 也许值得一提的是,第一个输入的默认值是红色。
了解了文档的结构后,请继续并按照如下方式进行设计:
- 将 容器
.slider
和.instructions
容器从文档流中取出。绝对定位它们。 - 给
body
元素一个日出的背景色,并在左下角用一朵花装饰背景 - 将
color-boxes
容器放在中心 - 调整
color-boxes
容器的样式
让我们把这些关掉。
以下将解决第一项任务。
/* Slider */
.slider,
.instructions {
position: absolute;
background: rgba(0,0,0,0.4);
padding: 1rem 2rem;
border-radius: 5px
}
.slider {
right: 10px;
top: 10px;
}
.slider > * {
display: block;
}
/* Instructions */
.instructions {
text-align: center;
bottom: 0;
background: initial;
color: black;
}
代码片段并不像你想象的那么复杂。我希望你能阅读并理解它。如果没有,请删除评论或推文。
造型body
更多一点。希望你能很好地理解CSS。
由于我们渴望使用背景色和背景图像对元素进行样式设置,因此使用background
简写属性设置多个背景也许是最佳选择。
这里是:
body {
margin: 0;
color: rgba(255,255,255,0.9);
background: url('http://bit.ly/2FiPrRA') 0 100%/340px no-repeat, var(--primary-color);
font-family: 'Shadows Into Light Two', cursive;
}
该url
位是日出花的链接。
下一组属性0 100%
表示图像的背景位置。
以下是CSS背景定位的工作原理图:
正斜杠后的另一位表示background-size
这已设置为340px
如果您将此设置得更小,图像也会变小。
no-repeat
,你可能会弄清楚它的功能。它可以防止背景重复。
最后,逗号后面的任何内容都是第二个背景声明。这一次,我们只设置background-color
到var(primary-color)
糟糕,这是一个变数。
这意味着你必须定义变量。就是这样:
:root {
--primary-color: rgba(241,196,15 ,1)
}
原色是日出黄色。没什么大不了。我们会尽快设置更多的变量。
现在,让我们来看看 color-boxes
main.booth {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
主容器充当柔性容器并将直接的孩子正确定位在页面的中心。这恰好是我们心爱的color-box
容器
让我们将颜色盒子容器和它的子元素变得漂亮。
首先,儿童元素:
.color-box {
padding: 1rem 3.5rem;
margin-bottom: 0.5rem;
border: 1px solid rgba(255,255,255,0.2);
border-radius: 0.3rem;
box-shadow: 10px 10px 30px rgba(0,0,0,0.4);
}
这将做到这一点。还添加了一个美丽的阴影。这会给我们带来一些很酷的效果。
这不是全部。让我们来设计整个container-boxes
容器:
/* Color Boxes */
.color-boxes {
background: var(--secondary-color);
box-shadow: 10px 10px 30px rgba(0,0,0,0.4);
border-radius: 0.3rem;
transform: perspective(500px) rotateY( calc(var(--slider) * 1deg));
transition: transform 0.3s
}
天啊!
那里有很多东西。
让我分解它。
这是简单的一点:
.color-boxes {
background: var(--secondary-color);
box-shadow: 10px 10px 30px rgba(0,0,0,0.4);
border-radius: 0.3rem;
}
你知道那是什么,嗯?
那里有一个新的变量。应该将其添加到根选择器中。
:root {
--primary-color: rgba(241,196,15 ,1);
--secondary-color: red;
}
次要颜色是红色。这会给容器一个红色的背景。
现在到可能让你困惑的部分:
/* Color Boxes */
.color-boxes {
transform: perspective(500px) rotateY( calc(var(--slider) * 1deg));
transition: transform 0.3s
}
一会儿,我们可以简化上面transform属性的值。
例如:
transform: perspective(500px) rotateY( 30deg);
变换速记应用了两种不同的功能。一个是角度,另一个是沿着Y轴的旋转。
嗯,那么perspective
和rotateY
功能有什么关系呢?
perspective()函数应用于正在3D空间中转换的元素。它激活三维空间并沿z轴给出元素深度。
你可以阅读更多关于透视功能codrops。
该rotateY
功能,有什么处理呢?
激活三维空间后,元素具有平面x,y,z。该rotateY
功能沿着Y
平面旋转元素。
来自codrops的以下图表对于可视化非常有帮助。
我希望能吹走一些蒸汽。
回到我们开始的地方。
当你移动滑块时,你知道什么函数影响旋转 .container-box
?
这是正在调用的rotateY函数。盒子沿着Y轴旋转。
由于传递到rotateY函数的值将通过JavaScript进行更新,因此该值由一个变量表示。
那么,为什么乘以1deg的变量呢?
作为一般的经验法则,为了明确自由,建议在构建单个令牌时,将值存储在没有单位的变量中。
您可以通过calc
函数进行乘法将它们转换为您想要的任何单位。
这允许你在拥有这些值时做任何你想要的。想要转换为deg
或作为用户视口的比例vw
,您可以随心所欲。
在这种情况下,我们通过将“数字”值乘以1deg来将该数字转换为具有程度
由于CSS不理解数学,因此必须将此算术传递给calc函数,以便通过CSS正确评估。
一旦完成,我们很好去。这个变量的值可以用JavaScript来更新,只要我们喜欢。
现在,只剩下一点CSS了。
这里是:
/* Handle colors for each color box */
.color-box:nth-child(1) {
background: var(--bg-1)
}
.color-box:nth-child(2) {
background: var(--bg-2)
}
.color-box:nth-child(3) {
background: var(--bg-3)
}
.color-box:nth-child(4) {
background: var(--bg-4)
}
.color-box:nth-child(5) {
background: var(--bg-5)
}
.color-box:nth-child(6) {
background: var(--bg-6)
}
那么,这是什么巫术?
首先,第n个子选择器选择每个子盒子。
这里需要一些远见。我们知道我们将更新每个盒子的背景颜色。我们也知道这个背景颜色必须用变量表示,所以可以通过JavaScript访问。对?
我们可以继续这样做:
.color-box:nth-child(1) {
background: var(--bg-1)
}
简单。
但有一个问题。如果这个变量不存在,会发生什么?
我们需要一个回退。
这工作:
.color-box:nth-child(1) {
background: var(--bg-1, red)
}
在这种特殊情况下,我选择不提供任何回退。
如果属性值中使用的变量无效,则属性将采用其初始值。
因此,当--bg-1
无效或不可用时,后台将默认为其初始值为透明。
初始值在未明确设置时引用属性的值。例如,如果你没有设置background-color
一个元素,它将默认为transparent
初始值是一种默认属性值。
我们来写一些JavaScript
我们需要在JavaScript的一面做很少的事情。
首先让我们来处理滑块。
我们只需要5行!
const root = document.documentElement
const range = document.querySelector('.booth-slider')
//as slider range's value changes, do something
range.addEventListener('input', handleSlider)
function handleSlider (e) {
let value = e.target.value
root.style.setProperty('--slider', value)
}
那很简单,是吧?
让我解释一下,以防万一我失去了你。
首先,保持对滑块元素的引用, const range = document.querySelector('.booth-slider')
为范围输入的值更改时设置事件侦听器, range.addEventListener('input', handleSlider)
写回调, handleSlider
function handleSlider (e) {
let value = e.target.value
root.style.setProperty('--slider', value)
}
root.style.setProperty('--slider', value)
说,获取root
元素(HTML),抓住它的风格,并设置一个属性。
处理颜色变化
这与处理滑块值更改一样简单。
就是这样:
const inputs = document.querySelectorAll('.color-box > input')
//as the value in the input changes, do something.
inputs.forEach(input => {
input.addEventListener('input', handleInputChange)
})
function handleInputChange (e) {
let value = e.target.value
let inputId = e.target.parentNode.id
let inputBg = `--bg-${inputId}`
root.style.setProperty(inputBg, value)
}
保留对所有文字输入的参考, const inputs = document.querySelectorAll('.color-box > input')
在所有输入上设置一个事件监听器:
inputs.forEach(input => {
input.addEventListener('input', handleInputChange)
})
编写handleInputChange
功能:
function handleInputChange (e) {
let value = e.target.value
let inputId = e.target.parentNode.id
let inputBg = `--bg-${inputId}`
root.style.setProperty(inputBg, value)
}
呼…
而已!
项目完成了。
我怎么错过了这个?
当我注意到我没有提到任何地方的浏览器支持时,我完成并编辑了本文的初稿。所以,让我解决我的烂摊子。
浏览器对CSS变量的支持(又名自定义属性)一点也不差。这非常好,在所有现代浏览器中都得到了不错的支持(撰写本文时为超过87%)。
那么,你今天可以在生产中使用CSS变量吗?我会说是!不过,请务必检查自己的采用率。
在光明的一面,你可以使用像神话一样的预处理器。它会预处理你今后使用的’未来’CSS。多酷,是吧?