除了CSS变量,CSS自定义属性还可以用于什么?

4

来自CSS工作组的文章:

自定义属性不仅用于变量[...]

这篇文章否定了很多人所认为的(参见这里),它不仅确认了变量和自定义属性不是同一回事,而且还表明自定义属性可以用于其他事情。

我的问题是关于最后一部分。有没有人遇到过使用自定义属性进行除了变量之外的其他事情的CSS代码?


@TemaniAfif 我删除了 [tag:custom-properties] 标签,因为该标签没有使用指南,被用于许多与 CSS 自定义属性无关的问题,并且因为 [tag:css-custom-properties] 已经被标记为更广泛认可的 [tag:css-variables] 的同义词。 - TylerH
3个回答

2
您链接的Stack Overflow答案实际上是错误的,并且与您所问的有些不同。它讨论了在相关CSS规范中,为了便于理解,通常将自定义属性描述为变量,尽管它们的工作方式与其他语言中的变量不同(但这在CSS中很普遍,不是吗?)。
CSS自定义属性的变量部分实际上是关于如何调用(或使用)自定义属性的:
.example {
    background: var(--custom-property);
}

正如您从规范中看到的那样,完整标题为“级联变量模块级别1的CSS自定义属性”,只有一个用途:作为级联元素中var()的值。因此,当那篇维基文章的作者说自定义属性不仅仅用于变量时,这个说法有点不清晰。此外,后续的句子并没有真正描述或支持这种说法,他们只是谈论使用$而不是-是“奇怪的”。

也许他们的意思是,您可以声明一个仅在JavaScript中读取时起作用的自定义属性?或者在JS中声明一个自定义属性,然后将其应用于CSS。自定义变量语法支持方程式,这些方程式可能无法被CSS解析器正确读取,但在JavaScript中可以正确读取。但是,结果仍然是使用var()声明的元素的属性值

为了更好地理解这个问题,如果自定义属性还有其他用途而不是在属性中使用var(),那么称它们为自定义属性就没有意义了。规范没有提到在CSS或JS中调用或使用属性的其他替代方法,而不是在级联选择器的属性值部分({}之间的代码位)中使用var()


1
现在,回答了这个问题之后,我已经联系了那篇维基文章的原作者,看看他们是否能够在这里提供一些澄清(或反驳)。 - TylerH
经过更多的探索,我在工作组的草案Wiki中找到了这个链接(请参见注释2,第2节)。 该注释指出:“注意:虽然此模块侧重于使用var()函数创建“变量”的自定义属性,但它们也可以用作实际的自定义属性,由脚本解析和执行。预计CSS扩展规范[CSS-EXTENSIONS]将扩展这些用例并使其更易于实现。” - Salim Mahboubi
@MahboubiSalim 是的,这与我关于JavaScript使用自定义属性的倒数第二段话相符。只是JavaScript在声明时而不是CSS(并且也在当前/发布版本的规范中)进行声明。 - TylerH
哦!我没看到编辑。提到了上面的草案链接这个它指出:“需要更全面地支持自定义属性(并最终将其从变量规范中完全移除,因为它们将在此处定义)。”所以这是一个正在进行中的工作,我们必须等待看看自定义属性是否会真正被使用而不需要var() - Salim Mahboubi
@MahboubiSalim 如果Tab能够提供一些澄清,那将非常有用。他是该引用和维基页面的作者,也是规范的主要作者,因此他具备独特的资格来回答。同时,希望这个答案(以及其他两个很好的答案)能够在某种程度上进行澄清。 - TylerH

2
其他问题解释了自定义属性和CSS变量,但如果我们简单地说自定义属性只是允许我们添加更多的属性而不是CSS默认属性,该怎么办呢?当然,一个自定义属性本身是无用的,因为浏览器不会处理它,这就是为什么在99%的情况下,它们与CSS变量一起使用。
另一个事实是,在编写CSS时,我们会直觉地想要使用默认的CSS属性来完成我们想要的操作,并且当我们面对复杂的情况时,我们会尝试结合许多属性、伪元素等来满足我们的需求。但是,如果我们换个思路,开始使用自定义属性来完成这样的目的呢?如果我们只是编写一个自定义属性,然后实现一个JS/jQuery代码来完成任务,而不是复杂的CSS代码,那该怎么办?
让我们以一个简单而常见的例子为例。您有一个元素,希望将其绝对定位,以便将其用作其父元素的覆盖。使用CSS,我们将执行以下操作:

.block {
  height:100px;
  width:100px;
  margin:20px; 
  border:1px solid red;
  position:relative; /* we usually forget this !*/
}

.overlay {
  position:absolute;
  top:0;
  right:0;
  left:0; /*sometimes we use width:100% but it won't work with padding!*/
  bottom:0;
  padding:5px;
  z-index:1; /* we can forget this sometimes*/
  background:rgba(0,0,0,0.5);
}
<div class="block">
<span class="overlay"></span>
  some text here
</div>

这很简单,如果我们想要同样的东西,我们可以在任何地方使用相同的类。但是我们可以考虑使用自定义属性来减少CSS代码并使其更容易:

$('*').each(function() {
  if ($.trim($(this).css('--overlay')) === "top") {
    $(this).css({
      'position': 'absolute',
      'top': 0,
      'bottom': 0,
      'left': 0,
      'right': 0,
      'z-index':2
    });
    $(this).parent().css('position','relative');
  } else {
    //we test the other values and we do the necessary changes
  }
})
.block {
  height: 100px;
  width: 100px;
  margin: 20px;
  border: 1px solid red;
}

.overlay {
  --overlay: top;
  background: rgba(0, 0, 0, 0.5);
}
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<div class="block">
  <span class="overlay"></span> 
  some text here
</div>

我知道你们的第一个想法是:“为什么要用这么多代码来实现我们可以用CSS轻松完成的事情?”以及“如果属性改变了怎么办?如果父元素已经设置了位置怎么办?”这是真的,但这只是一个非常简化的例子。想象一下,你构建了一个JS或jQuery库,其中提供了许多自定义属性,用于处理复杂的内容(创建常见的CSS形状、进行难以计算值的复杂转换等),并为每个属性提供相应的值文档。
就像一个JS/jQuery库告诉你给你的元素添加一个类并调用一个函数,然后你就会得到响应式幻灯片、交互地图、Twitter小部件等。为什么不使用自定义属性做同样的事情呢?我们要求人们包含库并简单地编写CSS,然后看到魔法发生。
我们还可以考虑这是通过添加更有用的属性来升级,以使事情变得更容易。就像CSS制作者所做的那样,但我们自己来做,不必等到有新规范并获得批准。
让我们以flexbox为例。在使用flexbox之前,如果我们想在容器中放置N个具有完全相同大小并填充整个容器的div,我们必须编写一些复杂的代码,每次添加新元素时都必须进行调整。现在使用flexbox,我们只需设置flex:1就可以了!浏览器将为我们处理所有复杂的事情。
我们可以完全做同样的事情。我们创建自定义属性来使事情变得更容易,并与社区分享。人们会开始使用它们并发现它们有用,然后它们可以成为解决常见问题的参考。也许某些浏览器甚至会考虑实现来集成它们。
因此,自定义属性也可以成为促进CSS改进的一种方式。

1
换句话说,在规模上它很有用……一个20行的CSS文件不值得使用自定义属性。但是当数量达到几百或几千时,它开始变得非常有用。 - TylerH
@TylerH 是的,这是其中一个主要目的,另一个目的是让新学习者能够轻松编写CSS,而无需了解所有内容...他们可以创建一个覆盖层,而不需要知道父级为什么需要是相对的,以及为什么使用top/right/left/bottom来拉伸和什么是z-index等等...当然,这不是一个好的例子,因为了解这些东西很重要,但对于更复杂的CSS,它可以有用地实现事情,而无需深入了解其工作原理。 - Temani Afif
@TylerH 我编辑了我的答案,包括另一个有用的用例 ;) - Temani Afif

2
请注意,您引用的文本背景是CSSWG证明为什么使用--foovar(--foo)代替$foo。我猜他们所说的“自定义属性不仅用于变量”是在暗示--foo标识符语法将出现在其他地方,用于其他自定义内容,例如最初在MQ4中的自定义媒体查询,现在在MQ5中。显然,那些不是级联变量,因为媒体查询不会级联(它们的封闭规则确实如此)。 (尽管,如果是这种情况,“自定义属性”应该是其他东西,但很可能CSSWG当时还没有考虑这些东西的适当集体名称。)

有没有人遇到过一些使用自定义属性进行变量以外操作的CSS代码?

我没有遇到任何真实世界的例子,但我刚刚编写了一个示例,该示例使用JavaScript基于自定义属性在元素上切换类,从而影响CSS中其他位置的选择器:

let p = document.querySelector('p');

if (window.getComputedStyle(document.documentElement).getPropertyValue('--boolean').trim() === "'true'")
  p.classList.add('toggle');
else
  p.classList.remove('toggle');
:root {
  --boolean: 'true';
}

p.toggle {
  color: #f00;
}

p::before {
  content: 'Value of --boolean is "' var(--boolean) '"';
}
<p>

当然,需要注意的是这是 CSS 和 JavaScript。你不能仅仅使用自定义属性来进行层叠变量,因为自定义属性像其他CSS属性一样参与级联,并且在其外部不存在。这就是为什么作者在日常使用中混淆了这两个术语的原因——因为它们之间的任何差异都是学术性的。样式表的任何其他部分如果想要依赖于自定义属性,则必须使用脚本才能使用它。
你可能会想知道为什么这样的东西不能在 CSS 中实现。考虑以下内容:
p.toggle {
  --boolean: 'false';
}

如果我们提出一个基于CSS属性值匹配元素的伪类,比如p:prop(--boolean: 'true')
  • should the pseudo-class be valid and always match, be valid and never match, or be invalid, outside of CSS?
  • what should the reflected value be? Specified value, computed value, or used value? Note that, for historical reasons, even window.getComputedStyle() does not always return computed values.
  • cyclic dependencies become inevitable:

    :root {
      --boolean: 'true';
    }
    
    /* 
     * Causes p to stop matching the selector, switching back to
     * inheriting from :root, which in turn causes it to start
     * matching the selector again, ad infinitum.
     */
    p:prop(--boolean: 'true') {
      --boolean: 'false';
    }
    
仔细观察上面的JavaScript示例,您会注意到它并不健壮。属性值只被评估一次,即在文档组合完成后立即执行。再次更改自定义属性值,元素不会更新自身以反映更改。如果它监听了自定义属性值的更改,它将立即遇到相同的循环依赖问题 - 更糟的是,它会锁定您的浏览器(直到它注意到并拦截无限递归)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接