【问题标题】:CSS scoped custom property ignored when used to calculate variable in outer scope用于计算外部范围内的变量时,CSS 范围内的自定义属性被忽略
【发布时间】:2020-07-04 01:11:20
【问题描述】:

我正在尝试通过var 自定义属性来缩放大小,以使类可以在不耦合的情况下组合。期望的效果是 3 个列表将具有 3 个不同的比例,但 demonstrated on CodePen 所有 3 个列表的比例相同。我正在寻找关于范围和 CSS 自定义属性技术的解释,该技术可以通过可组合的松散耦合代码实现这一目标。

:root {
  --size-1: calc(1 * var(--scale, 1) * 1rem);
  --size-2: calc(2 * var(--scale, 1) * 1rem);
  --size-3: calc(3 * var(--scale, 1) * 1rem);
}

.size-1 { font-size: var(--size-1) }
.size-2 { font-size: var(--size-2) }
.size-3 { font-size: var(--size-3) }

.scale-1x { --scale: 1 }
.scale-2x { --scale: 2 }
.scale-3x { --scale: 3 }

html {
  font: 1em sans-serif;
  background: papayawhip;
}

ol {
  float: left;
  list-style: none;
  margin: 1rem;
}
<ol class="scale-1x">
  <li class="size-1">size 1</li>
  <li class="size-2">size 2</li>
  <li class="size-3">size 3</li>
</ol>
<ol class="scale-2x">
  <li class="size-1">size 1</li>
  <li class="size-2">size 2</li>
  <li class="size-3">size 3</li>
</ol>
<ol class="scale-3x">
  <li class="size-1">size 1</li>
  <li class="size-2">size 2</li>
  <li class="size-3">size 3</li>
</ol>

【问题讨论】:

  • @Temani Afif:变量是 CSS3,因为工作组将 CSS3 定义为 CSS2 之外的所有内容,而不是因为变量具有交付实现。 “CSS4”不是官方术语。请注意,变量从第 1 级开始。人们将某些第 3 级或第 1 级功能分类为 CSS3 和其他分类为“CSS4”的方式......很吸引人,说得好听。
  • 如果仍然感兴趣,我在我的答案中添加了另一个解决方案的想法,这可能是一个好技巧(用通用选择器 * 替换 :root

标签: css scope css-variables


【解决方案1】:

在您的情况下,您已经在根级别评估了 --scale 自定义属性以定义 --size-* 属性,然后您在子元素中定义了 --scale 再次。这不会再次触发评估,因为它已经在上层中完成。

这里有一个简单的例子来说明这个问题:

.box {
  --color: var(--c, blue);
}

span {
  color: var(--color);
}
<div>
  <div class="box"><!-- --c is evaluated at this level -->
    <span style="--c:red">I will not be red because the property is already evaluated and --color is set to blue using the default value</span>
  </div>
</div>

<div style="--c:red">
  <div class="box"><!-- --c is evaluated at this level -->
    <span>I will be red because at the time of the evaluation --c is red (inherited from the upper div)</span>
  </div>
</div>

要解决您的问题,您需要将声明从 :root 移动到与 --scale 定义相同的级别:

.scale {
  --size-1: calc(1 * var(--scale, 1) * 1rem);
  --size-2: calc(2 * var(--scale, 1) * 1rem);
  --size-3: calc(3 * var(--scale, 1) * 1rem);
}

.size-1 { font-size: var(--size-1) }
.size-2 { font-size: var(--size-2) }
.size-3 { font-size: var(--size-3) }

.scale-1x { --scale: 1 }
.scale-2x { --scale: 2 }
.scale-3x { --scale: 3 }


html {
  font: 1em sans-serif;
  background: papayawhip;
}

ol {
  float: left;
  list-style: none;
  margin: 1rem;
}
<ol class="scale-1x scale">
  <li class="size-1">size 1</li>
  <li class="size-2">size 2</li>
  <li class="size-3">size 3</li>
</ol>
<ol class="scale-2x scale">
  <li class="size-1">size 1</li>
  <li class="size-2">size 2</li>
  <li class="size-3">size 3</li>
</ol>
<ol class="scale-3x scale">
  <li class="size-1">size 1</li>
  <li class="size-2">size 2</li>
  <li class="size-3">size 3</li>
</ol>

在这种情况下,--scale 定义在与其评估相同的级别,因此 --size-* 将针对每种情况正确定义。


来自the specification

用 var() 替换属性值:

  1. 如果自定义属性由 var() 的第一个参数命名 函数被动画污染,并且 var() 函数被用于 动画属性或其长手之一,对待自定义 属性具有该算法其余部分的初始值。
  2. 如果自定义属性的值由第一个参数命名为 var() 函数不是初始值,替换 var() 通过相应自定义属性的值起作用。否则,
  3. 如果 var() 函数有一个备用值作为其第二个参数, 用回退值替换 var() 函数。如果有的话 var() 后备中的引用,也可以替换它们。
  4. 否则,包含 var() 函数的属性在 计算值时间

在您的第一种情况下,您将陷入 3,因为在根级别没有为 --scale 指定值。在最后一种情况下,我们陷入了 2,因为我们在同一级别定义了 --scale,并且我们有它的值。


在所有情况下,我们都应该避免在:root 级别进行任何评估,因为它根本没有用。根级别是 DOM 中的最高级别,因此所有元素都将继承相同的值,并且除非我们再次评估变量,否则 DOM 内不可能有不同的值。

你的代码相当于这个:

:root {
  --size-1: calc(1 * 1 * 1rem);
  --size-2: calc(2 * 1 * 1rem);
  --size-3: calc(3 * 1 * 1rem);
}

我们再举一个例子:

:root {
  --r:0;
  --g:0;
  --b:255;
  --color:rgb(var(--r),var(--g),var(--b))
}
div {
  color:var(--color);
}
p {
  --g:100;
  color:var(--color);
}
<div>
  some text
</div>
<p>
  some text
</p>

直觉上,我们可能认为我们可以通过更改在:root 级别定义的 3 个变量之一来更改 --color,但我们不能这样做,上面的代码与这个相同:

:root {
  --color:rgb(0,0,255)
}
div {
  color:var(--color);
}
p {
  --g:100;
  color:var(--color);
}
<div>
  some text
</div>
<p>
  some text
</p>

这 3 个变量(--r--g--b)在 :root 内部计算,所以我们已经用它们的值替换了它们。

在这种情况下,我们有 3 种可能性:

  • 使用 JS 或其他 CSS 规则更改 :root 中的变量。这不会让我们有不同的颜色:

:root {
  --r:0;
  --g:0;
  --b:255;
  --color:rgb(var(--r),var(--g),var(--b))
}
div {
  color:var(--color);
}
p {
  --g:200; /*this will not have any effect !*/
  color:var(--color);
}

:root {
  --g:200; /*this will work*/
}
<div>
  some text
</div>
<p>
  some text
</p>
  • 在所需元素内再次计算变量。在这种情况下,我们将失去任何灵活性,:root 内部的定义将变得无用(或至少将成为默认值):

:root {
  --r:0;
  --g:0;
  --b:255;
  --color:rgb(var(--r),var(--g),var(--b))
}
div {
  color:var(--color);
}
p {
  --g:200;
  --color:rgb(var(--r),var(--g),var(--b));
  color:var(--color);
}
<div>
  some text
</div>
<p>
  some text
</p>
  • :root 选择器更改为通用选择器*。这将确保我们的函数在所有级别都得到定义和评估。在某些复杂的情况下,这可能会产生一些不需要的结果

* {
  --r:0;
  --g:0;
  --b:255;
  --color:rgb(var(--r),var(--g),var(--b))
}
div {
  color:var(--color);
}
p {
  --g:200;
  color:var(--color);
}
<div>
  some text
</div>
<p>
  some text
</p>

考虑到这一点,我们应该始终将评估保持在 DOM 树中的最低点,尤其是在变量更改之后(或在同一级别)

这是我们不应该做的事

:root {
  --r: 0;
  --g: 0;
  --b: 0;
}
.color {
  --color: rgb(var(--r), var(--g), var(--b))
}
.green {
  --g: 255;
}
.red {
  --r: 255;
}
p {
  color: var(--color);
}

h1 {
  border-bottom: 1px solid var(--color);
}
<div class="color">
  <h1 class="red">Red </h1>
  <p class="red">I want to be red :(</p>
</div>
<div class="color">
  <h1 class="green">Green </h1>
  <p class="green">I want to be green :(</p>
</div>

这是我们应该做的

:root {
  --r:0;
  --g:0;
  --b:0;
}
.color {
  --color:rgb(var(--r),var(--g),var(--b));
}

.green {
  --g:255;
}

.red {
  --r:255;
}

p {
  color:var(--color);
}
h1 {
  border-bottom: 1px solid var(--color);
}
<div class="red">
  <h1 class="color">Red title</h1>
  <p class="color">Yes I am red :D</p>
</div>
<div class="green">
  <h1 class="color">Green title</h1>
  <p class="color">Yes I am green :D</p>
</div>

我们也可以这样做:

:root {
  --r:0;
  --g:0;
  --b:0;
}
.color {
  --color:rgb(var(--r),var(--g),var(--b));
}

.green {
  --g:255;
}

.red {
  --r:255;
}

p {
  color:var(--color);
}
h1 {
  border-bottom: 1px solid var(--color);
}
<div class="red color">
  <h1 >Red title</h1>
  <p >Yes I am red :D</p>
</div>
<div class="green color">
  <h1>Green title</h1>
  <p >Yes I am green :D</p>
</div>

【讨论】:

  • 是的,但如果 CSS vars 确实存在,那么它为什么不继承和评估呢?我想避免让比例类知道大小变量。有办法吗?
  • @ryanve 因为 CSS 是从上到下的……假设您正在使用继承,父元素可以从其子元素继承吗?不......这里的逻辑相同,根在评估时看不到 --scale 属性。因此,如果一个子元素更改了一个属性,我们将不会向后重新评估所有父属性,这将创建循环并且它不会起作用
  • @TemaniAfif 我同意你在上一条评论中所说的话,但这并不意味着像stackoverflow.com/questions/63459791 这样的事情是不可能的。在那个例子中,孩子没有(试图)重写父值,但它仍然不起作用。您在此处的回答表明,我们只能制作依赖于 DOM 结构(或标记)的可重用 CSS“功能”,但我们不能制作可在 CSS 中重用且不依赖 DOM 的可重用“功能”结构(标记)。这意味着可重用性与 DOM 结构严格耦合,这使得可重用性变得复杂。
  • @TemaniAfif 基本上我的意思是,就像在这个例子中(codepen.io/trusktr/pen/c3f10cf692bb40de403f6374cd9c54f1),没有办法给 CSS 作者一个可重用的函数而不要求他们修改 HTML 标记。没有办法让它们严格地留在 CSS 中;他们必须在标记中使用sum 类来实现可重用性。
  • @trusktr 在那个例子中,孩子不会重写父值, --> 这不是关于重写,而是关于范围。一个元素看不到其子元素中定义的内部范围,因此更改子元素中的值永远不会更改父元素中的值。 CSS从上到下。你想要的不可能以你想要的方式。在根目录中使用带有 CSS 变量的函数是没有用的,你无法克服这个问题(唯一的方法是我在这个答案中描述的那些)
猜你喜欢
相关资源
最近更新 更多