:root { --color: 20px; }
p { background-color: red; }
p { background-color: var(--color); }
正如我们所料,--color
变量会在 var()
中被替换,但是替换后,属性值 background-color: 20px
是非法的。由于 background-color
不是可继承的属性,属性值将默认被替换成它的初始值即 transparent
。
注意,如果你没有通过变量替换,而是直接写 background-color: 20px
的话,这个背景属性声明就是非法的,则使用之前的声明定义。
当你自己写声明时,情况就不一样了。
使用单独符号时要小心
当你用下面这种方式来设置属性值时,20px
则会按照单独符号来解析。
font-size: 20px
复制代码
有一个简单的方法去理解,20px
这个值可以看作是一个单独的 “实体”。
在使用 CSS 变量构建单独符号时需要非常小心。
举个例子,思考以下代码:
:root {
--size: 20
div {
font-size: var(--size)px
复制代码
可能你会以为 font-size
的值是 20px
,那你就错了。
浏览器的解释结果是 20 px
请注意 20
后面的空格
因此,如果你必须创建单独符号的话,请用变量来代表整个符号。比如 --size: 20px
,或者使用 calc
函数比如 calc(var(--size) * 1px)
中的 --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;
.btn:hover {
cursor: pointer;
background: black;
color: white;
复制代码
在哪里引入变化量呢?
.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;
.btn:hover {
cursor: pointer;
background: var(--color, black);
color: white;
复制代码
当你写下 background: **var(--color, black)**
时,就是将背景色的值设置为变量 --color
的值,如果变量不存在的话则使用默认值 **black**
这就是设置变量默认值的方法,与在 JavaScript 和其它语言中的做法一样。
这是使用变量的好处。
使用了变化量,就可以用下面这种方法来应用变量的新值:
.btn.red {
--color: red
复制代码
就是这么简单。现在当使用 .red
类时,浏览器注意到不同的 --color
变量值,就会立即更新按钮的样式了。
如果你要花很多时间来构建可复用组件的话,使用 CSS 变量是一个非常好的选择。
这是并排比较:
不用 CSS 变量 VS 使用 CSS 变量。
如果你有非常多的可变选项的话,使用 CSS 变量还会为你节省很多打字时间。
看出不同了吗??
项目 2: 使用 CSS 变量实现主题定制
我很确定你之前一定遇到过主题定制的需求。支持主题定制的站点让用户有了自定义的体验,感觉站点在自己的掌控之中。
下面是我写的一个简单示例:
使用 CSS 变量来实现有多么容易呢?
我们来看看。
在此之前,我想提醒你,这个示例非常重要。通过这个示例我将引导你理解使用 JavaScript 更新 CSS 变量的思想。
非常好玩!
你会爱上它的!
我们究竟想做什么。
CSS 变量的美在于其本质是响应式的。一旦 CSS 变量更新了,任意带有 CSS 变量的属性的值也都会随之更新。
从概念上讲,下面这张图解释了这个示例的流程。
因此,我们需要给点击事件监听器写一些 JavaScript 代码。
在这个简单的示例里,文本与页面的颜色和背景色都是基于 CSS 变量的。
当你点击页面上方的按钮时,JavaScript 会将 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
。因此知道是哪个按钮被点击以及后续该执行什么正确操作很重要。
鉴于此,我们使用了 switch 操作符
,基于被点击的按钮的值来执行不同的操作。
接下来再看一遍这段 JavaScript 代码,你会理解地更好一些。
项目 3: 构建 CSS 变量展位
避免你错过它,这是我们即将构建的项目:
请记住盒子的颜色是动态更新的,以及盒子容器是随着输入范围值的变化进行 3D 旋转的。
你可以直接在 Codepen 上玩一下这个项目。
这是使用 JavaScript 更新 CSS 变量以及随之而来的响应式特性的绝佳示例。
让我们来看看如何来构建。
以下是所需的组件。
一个范围输入框
一个装载使用说明文字的容器
一个装载盒子列表的 section,每个盒子包含输入框
结构变得很简单。
以下就是:
<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
范围的值,step 值为 5
。因此范围输入的最小值就是 -50
如果你并不确定范围输入是否可以运行,可以在 w3schools 上检查以下
注意类名为 .color-boxes
的 section 是如何包含其它 .color-box
容器的。这些容器中包含输入框。
第一个输入框有默认值为 red。
理解了文档结构后,给它添加样式:
把 .slider
和 .instructions
设置为脱离文档流,将它们的 position 设置为 absolute
将 body
元素的背景色设置为日出的颜色,并在左下角用花朵作装饰
将 color-boxes
容器定位到中间
给 color-boxes
容器添加样式
让我们把这些任务都完成。
以下代码会完成第一步。
.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 {
text-align: center;
bottom: 0;
background: initial;
color: black;
复制代码
这段代码并不像你想的那般复杂。希望你能通读一遍并能读懂,如果没有的话,可以留下评论或者发个 twitter。
给 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 position 属性是如何工作的:
来自于: CSS 进阶指南
来自于: 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;
复制代码
主容器充当 flex 容器,它的子元素会正确地被定位到页面中间。也就是说我们的 color-box
容器会被定位到页面中间。
我们把 color-boxes 以及它的子元素容器变得更好看一些。
首先,是子元素:
.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 {
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 {
transform: perspective(500px) rotateY( calc(var(--slider) * 1deg));
transition: transform 0.3s
复制代码
又是我们会将 transform 的属性值简写成上面这样。
举个例子:
transform: perspective(500px) rotateY( 30deg);
复制代码
这个 transform 简写用了两个不同的函数。一个是视角,另一个是沿着 Y 轴旋转。
那么 perspective
函数 和 rotateY
函数是做什么的呢?
perspective() 函数应用于 3D 空间内旋转的元素。它激活了三维空间,并沿 z 轴给出元素的深度。
可以在 codrops 上阅读更多有关 perspective 的知识。
rotateY
函数是干什么的?
激活三维空间后,元素具有了 x,y,z 轴。rotateY
就是元素围绕 Y
平面旋转。
下面这个 codrops 的图对于视觉化理解很有帮助。
Codrops
我希望这能让你更明白一些。
回到之前的话题。
当你回到这里,你知道哪个函数影响 .container-box
的旋转了吗?
是 rotateY 函数使得盒子沿着 Y 周旋转。
由于传入 rotateY 函数的值将被 JavaScript 更新,这个值也将通过变量来传入。
为什么要给变量乘上 1deg?
作为一般的经验法则,为了显式地更灵活,建议在构建单独符号时变量中储存没有单位的值。
通过 calc
函数,你可以用乘法将它们转化成任何单位。
这意味着你可以为所欲为。将作为比例的 deg
转换为视窗单位 vw
也可以。
在这个场景中,我们通过 “数字” 乘上 1deg 将数字转换成角度
由于 CSS 不懂数学,你需要将公式传入 calc 函数,这样 CSS 才能正确计算。
完成之后我们就可以继续了。我们可以在 JavaScript 中用各种方法来更新它。
现在,只剩下一点点的 CSS 代码需要写了。
就是这些:
.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)
复制代码
这些奇怪的东西是什么?
首先,nth-child 选择器用来选择子盒子。
这里需要一些前瞻。我们知道,每个盒子的背景色都会更新。我们也知道背景色需要用变量表示,以在 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 这一边需要做的事情很少。
首先要处理一下 slider。
仅仅五行代码就可以!
const root = document.documentElement
const range = document.querySelector('.booth-slider')
range.addEventListener('input', handleSlider)
function handleSlider (e) {
let value = e.target.value
root.style.setProperty('--slider', value)
复制代码
这很简单,对吧?
我来解释一下。
首先,保存一份 slider 元素的引用,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),读取它的样式,并给它设置属性。
处理颜色变化
这与处理 slider 值的变化一样简单。
这么做就可以:
const inputs = document.querySelectorAll('.color-box > input')
// 一旦输入值发生变化,执行回调
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%)。
caniuse
那么,你可以在生产环境使用 CSS 变量吗?当然可以!但是这多大程度上适用于你还需自己判断。
好的一面是,你可以使用像 Myth 这样的预处理器来使用 CSS 变量。它将“未来的” CSS 预编译成现在你就可以使用的代码,是不是很赞?
如果你有使用 postCSS 的经验, 这也同样是一个好方法。这是 postCSS 的 CSS 变量模块。
就这些,我已全部写完。
不好,我遇到了问题!
购买电子书 可以线上阅读, 还能获得 私人的 slack 邀请,你可以向我咨询任何问题。
这是个公平交易,对吧?
稍后联系! 💕
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。