我最近看到这个问题困扰了不止几个人,包括我自己,所以我要把它记录下来。
让我们将一些自定义属性添加到 CSS 中
html {
--color-1: red;
--color-2: blue;
}
让我们立即使用它们来创建一个背景渐变
html {
--color-1: red;
--color-2: blue;
--bg: linear-gradient(to right, var(--color-1), var(--color-2));
}
现在假设页面上有几个 div
<div></div>
<div class="variation"></div>
让我对它们进行样式设置
div {
background: var(--bg);
}
这完全有效!太棒了!
现在让我对那个变化进行样式设置。我不想让它从红色变为蓝色,而是想让它从绿色变为蓝色。很简单,我会将红色更新为绿色
html {
--color-1: red;
--color-2: blue;
--bg: linear-gradient(to right, var(--color-1), var(--color-2));
}
div {
background: var(--bg);
}
.variation {
--color-1: green;
}
不行!(警报声大作,喇叭声喧嚣,家畜躲避起来)。
朋友们,这行不通。
据我所知,问题在于 --bg
从未在任何一个 div 上声明过。它可以使用 --bg
,因为它是在更高级别上声明的,但在它被使用时,它的值就被锁定了。仅仅因为您更改了 --bg
在声明时碰巧使用的某个其他属性,并不意味着该属性会出去搜索它被使用的所有位置并更新所有依赖它的东西。
哎,这个解释不太对。但我只能想到这些。
解决方案?其实有很多。
解决方案 1:将变量作用域限定在您使用它的位置。
您可以这样做
html {
--color-1: red;
--color-2: blue;
}
div {
--bg: linear-gradient(to right, var(--color-1), var(--color-2));
background: var(--bg);
}
.variant {
--color-1: green;
}
现在,--bg
已在两个 div 上声明,对 --color-1
依赖项的更改确实有效。
解决方案 2:用逗号分隔您设置大多数变量的选择器。
假设您做了常见的操作,即在 :root
上设置了许多变量。然后您就会遇到这个问题。您只需在该主要声明中添加其他选择器,以确保您命中了正确的范围。
html,
div {
--color-1: red;
--color-2: blue;
--bg: linear-gradient(to right, var(--color-1), var(--color-2));
}
div {
background: var(--bg);
}
.variation {
--color-1: green;
}
在其他可能不太牵强的示例中,它可能看起来像这样
:root,
.button,
.whatever-it-is-a-bandaid {
--padding-inline: 1rem;
--padding-block: 1rem;
--padding: var(--padding-block) var(--padding-inline);
}
.button {
padding: var(--padding);
}
.button.less-wide {
--padding-inline: 0.5rem;
}
解决方案 3:覆盖模式
去它的吧——把变量放在任何地方。
* {
--access: me;
--whereever: you;
--want: to;
--hogwild: var(--access) var(--whereever);
}
这不是一个好计划。我最近听到有人在聊天中说,一个中等规模的网站的页面渲染延迟了 500 毫秒,因为页面的每次绘制都需要计算所有属性。它“有效”,但它属于少数情况下,您可以在选择器中导致真正的性能问题。
解决方案 4:引入新的“默认”属性和回退
此处所有功劳归属于 Stephen Shaw,他是 所有这些问题的探索者,我最初就是在那里看到这个混淆的。
让我们回到我们对这个问题的第一个演示
html {
--color-1: red;
--color-2: blue;
--bg: linear-gradient(to right, var(--color-1), var(--color-2));
}
我们要做的是给自己两样东西
- 一种覆盖整个背景的方法
- 一种覆盖渐变背景部分的方法
所以我们将用这种方法来做
html {
--color-1: red;
--color-2: blue;
}
div {
--bg-default: linear-gradient(to right, var(--color-1), var(--color-2));
background: var(--bg, var(--bg-default));
}
请注意,我们根本没有声明 --bg
。它只是在那里等待一个值,如果它获得了值,那么该值就是“获胜”的值。但是如果没有值,它将回退到我们的 --bg-default
。现在…
- 如果我设置
--color-1
或--color-2
,它将按预期替换渐变的对应部分(只要我在影响其中一个 div 的选择器上执行此操作)。 - 或者,我可以将
--bg
设置为将整个背景重置为我想要的任何内容。
感觉是一种处理事物的好方法。
有时,确实存在关于 CSS 自定义属性的错误。但这并不是其中之一。尽管对我来说这有点像错误,但它似乎不是。只是您需要了解的事情之一。
CSS 变量的黄金法则是在根级别不要进行任何评估。这将使变量变得毫无用处,因为根元素(html)是 DOM 树中最顶层的元素,所有元素只会继承“结果”,而不是“公式”。
我一直在使用的相关 SO 问题:https://stackoverflow.com/q/52015737/8620333(是的,人们总是会遇到这种非直观的行为,并在 SO 上问同样的问题)
也就是说,正在讨论创建 CSS 函数来克服这个问题:https://github.com/w3c/css-houdini-drafts/issues/1007。
我真的很不喜欢为了让这个 CSS 按我们希望的方式运行而必须以某种方式标记 html。在我看来,这奇怪地耦合了关注点。
虽然这是一个问题,但唯一正确的解决方案是 1. 4 只是添加了一个完全不必要的双重覆盖层。
--bg
有什么意义?我要么覆盖div { background: ...}
,要么覆盖div {--color1: ...}
。请不要再犯我之前犯的错误了 :-)我更喜欢 4. 我喜欢它提供了使用选项。
我知道它不是纯 CSS,但对于这样的问题,SASS 很出色。您可以将颜色声明为 CSS 变量,然后将渐变声明为 SASS 混合。SASS 编译后,您最终将获得直接定义在 div 内部的渐变,而不会失去使用 CSS 变量来控制它的灵活性。就像这样
或者您可以通过将 CSS 变量包含在混合中来简化它
我在一个地方(:root)定义所有自定义属性,并且不在本地重新定义属性。这有助于避免诸如此类的问题,以及总体上的复杂性。
`:root {
–color-1: red;
–color-2: blue;
–color-3: green;
}
div {
background: var(–bg-grad);
}
.variation {
background: var(–bg-grad-alt);
}`
linear-gradient()
在:root
中。嗯,
calc()
似乎在:root
中不起作用。至少对我来说是这样 :)
有人可以确认吗?
我想知道其他函数是否在
:root
中不起作用。也许
:root
声明是在浏览器渲染任何内容之前设置的,因此尺寸(我假设calc()
的最常见用途)不可用?