早在 2020 年 7 月,我收到一封来自 James0x57(我总是试图用他们的名字称呼人,但我认为我感觉他们更喜欢用屏幕名字)的电子邮件,上面写着
自定义 CSS 属性的分支条件逻辑和批量功能切换的整个世界都是可能的,并且只存在于 CSS 规范中一个被忽视的小脚注。
那一行是
注意:虽然 <declaration-value> 必须至少表示一个标记,但该标记可以是空白符。
换句话说,--foo: ;
是有效的。
如果你和我一样,这听起来并不像什么重大的发现,它打开了巨大的大门,但对像 James0x57 这样的聪明人来说,它确实如此!我们开始着手撰写一篇博客文章草稿,但由于种种原因,它最终没有在这里发表。其中一个原因是我根本没明白。叫我笨吧,对不起,James0x57。不过,他们在我要求一个非常简化的例子时给我发送的一个演示很有用,我认为我有点明白了。这是我的解释
让我试着解释一下
- 我们在这里设置的断点是一个 900px 的
max-width
媒体查询。你可以看到,变量--mq-sm
从initial
切换到一个空值的地方就是断点。 - 当浏览器窗口宽度大于 900px 时,
--mq-sm
的值为initial
。- 这使得变量
--padding-when-small
包含两个值——initial
和2rem
——我想这是无效的。 - 因此,当我们真正设置内边距并调用该变量(例如
padding: var(--padding-when-small, var(--padding-when-large))
)时,会使用第二个值(“回退”值),因为第一个值无效。
- 这使得变量
- 当浏览器窗口宽度小于 900px 时,
--mq-sm
的值为一个空格。- 这使得变量
--padding-when-small
的值为"(空格)2rem"
,我想这是有效的。 - 这意味着,当我们真正设置内边距并调用该变量(例如
padding: var(--padding-when-small, var(--padding-when-large))
)时,会使用第一个值。
- 这使得变量
因此,现在我们可以通过更改一个占位符变量来在两个值之间切换内边距。
我明白了。
当我看到这仅仅是更改一个单一值时,这几乎就像呃,好吧,你找到了一种非常复杂的方法来更改一些内边距,但你本可以在媒体查询中直接更改内边距。但技巧在于现在我们有了这个已更改的占位符变量,我们可以利用它来更改无限数量的其他值。
我们可以在 CSS 中使用单个媒体查询(或一组媒体查询)来切换这些占位符变量,然后我们在其他地方使用它们来切换值。与在整个 CSS 中散布媒体查询相比,这样可能会更好、更简洁。这是一种在 CSS 中进行真正的切换,就像一种我们以前从未有过的 IF/THEN 逻辑形式。
James0x57 将这种想法扩展到了所有逻辑可能性,例如 AND、OR、XOR、NAND、NOR 和 XNOR,但这又让我迷茫了。我不是一个真正的计算机科学家。但你可以关注他们的工作,如果你想看到这些东西的实际应用。
这些变量很疯狂,而且非常令人困惑。我在 Patrick Brosset 的一篇可能很新(但署名日期是 2015 年?)文章中注意到了一些关于 CSS 自定义属性的棘手问题。例如,回退可以无限嵌套,比如
color: var(--foo, var(--bar, var(--baz, var(--are, var(--you, var(--crazy)))));
此外,CSS 自定义属性的有效值可以包含逗号,例如
content: var(--foo, one, two, three);
这真的是一个带有一个 one, two, three
值的单一回退吗?这真是令人头疼。
无论如何,现在快进几个月,CSS 技巧大师 Lea Verou 已经将目光瞄准了自定义属性中的空白符。
如果我告诉你,你可以使用一个属性值来打开和关闭多个不同属性(甚至跨多个 CSS 规则)的多个不同值,你会怎样想?
这与之前的技巧相同!不过,在 Lea 的例子中,她使用这种能力来
- 设置按钮的变体,以及
- 设置四个不同的属性而不是一个。
这确实突出了这个概念为什么如此酷。
Lea 指出了它的一些缺点
没有办法说“如果
--foo
已设置,则背景应为红色,否则应为白色”。一些这样的条件可以通过巧妙地使用追加来模拟,但大多数情况不行。当然,还存在一定的可读性问题:
--foo: ;
看起来像个错误,而--foo: initial
看起来也很奇怪,除非你了解这种技术。
我们无疑正在进入自定义属性使用方式的新时代。首先,我们像预处理器变量一样使用它们。然后我们开始看到更多级联和回退的使用。接下来,我们更频繁地将它与 JavaScript 结合使用。现在是这个。
甚至还有更多关于保留 CSS 预处理器变量的文章,不仅仅是在你只需要它们的功能时才保留它们,而是为了保留它们特有的功能,例如操作它们的彩色值。
嘿,干得漂亮!
但我找不到这种技术的纯 CSS NOT 操作......AND 和 OR 很简单,但如何进行 NOT 操作呢?
有两种方法。(就我目前所知,使用今天可用的工具)
一种是使用暂停的帧动画进行切换——这不太好,但这是一个演示:https://codepen.io/james0x57/pen/mdExrXy
另一种方法是我在大多数 css-sweeper 中使用的:当设置初始状态时,还将一个第二个变量设置为逻辑反值。从那里开始,当你需要在逻辑中进行 NOT 操作时,你必须将你正在做的事情镜像到 NOT 位上,并将其与主位一起进行。基本上,它使用两个位来表示同一个位的不同状态——就像 DNA、夸克、磁铁等在任何时候都有镜像对一样。代码中有许多内容,但我无法链接到具体行:https://raw.githubusercontent.com/propjockey/css-sweeper/master/index.html 这是该设置的截图:https://i.imgur.com/aho538M.png
是这样吗?
好的......由于“布尔值”永远不会从 calc() 中弹出,因此你可以将 NOT 值保持为显式。我明白了。
动画技术非常有趣!
感谢你的回复!
如果你的 CSS 压缩器将
--mq-sm: ;
更改为--mq-sm:;
,这个技巧就会失效,对吧?对我来说似乎是这样。我非常想知道这些异常行为在不同浏览器中的一致性实现程度,以及它们在各种压缩器等中生存的程度。是的,你必须避免使用像 CSSNano 这样的压缩器,它基于 PostCSS。在 React 中,这意味着手动将 CSS 包含在 /public/index.html 文件中:https://create-react-app.reactjs.ac.cn/docs/using-the-public-folder/
@jon_neal 一直在进行一些很棒的工作,编写处理此问题(以及 PostCSS 中的许多其他问题区域)的 CSS 解析器,因此希望他的工作能够融入该生态系统。
这是一种使用 CSS 变量的常用技巧,有些状态将变量设置为“unset”,从而强制使用回退值。我以前玩过这个。CSS 有很多隐藏的惊喜,只需要深入挖掘;)
我发明了一种与本文中提到的方法类似的方法
你应该阅读 StackOverflow 答案以获取更多详细信息。
(第二种方法真的很有趣)
这很酷,因为它能够知道某个数值变量何时达到某个目标值,然后从那里控制一切。我找到了创建 CSS 布尔值的方法,这实际上很有用。
我在我的 Pen 中使用了这种技术:https://codepen.io/vsync/pen/mdEJMLv
我想要一个“一变量统治所有”的解决方案,所以我发明了一个很小的技巧,我本来想在这里写一下,但我还没写,因为我正在 Codepen 中为 Codepen 用户构建一些很棒的东西!在构建的过程中,我还做了一些很酷的东西......
本周就会准备好。
你会喜欢的。
一个更简单的版本:https://codepen.io/CarterLi/pen/xxOWWyX
@carter——它可能更短,但它忽略了你想使用一个布尔变量来控制 CSS 的多个方面,因此你需要一个真正的单变量
--bool
,或者任何名称。你的代码只影响简单的事情(我的演示也是如此,但我认为它传达了这一点,它以某种方式展示了如何计算 0/1 布尔值可以在许多情况下使用,其中之一就是使用关键帧)。想象一下,你有 400 行 CSS,并且你在许多代码中的许多方式中都包含了这个布尔变量。你不会想要为每种情况都创建关键帧。首先,你尝试尽一切可能不使用关键帧技巧,只有在不可能的情况下才使用它,因为它会生成太多 CSS 并使用命名关键帧来污染全局环境。
我想
--mq-sm: ;
是一个有效的语句,因为未类型化的 CSS 变量是惰性解析的。只要有字符可解析,包括空格,值就是有效的。我认为未类型化的 CSS 变量非常类似于 C 中的宏,它们只有在某处使用时才具有意义。
但是
--mq-sm: initial
是一个例外。--mq-sm: initial
的行为类似于--mq-sm
完全未定义,而不是值initial
。似乎浏览器在定义自定义属性时识别 CSS 关键字
initial
unset
inherit
。事实是,如果initial
未被识别,--padding-when-small: var(--mq-sm) 2rem
应该解析为---padding-when-small: initial 2rem
,而initial 2rem
是 CSS 属性的完美有效值。另一个证明是,如果我们将
--mq-sm
定义为initial
以外的任何东西,该示例将不起作用。因此,事实是
--mq-sm: initial
的行为类似于--mq-sm
未定义,这使得var(--mq-sm)
成为一个无效表达式。由于var(--mq-sm)
无效,--padding-when-small: var(--mq-sm) 2rem;
是一个无效表达式,这使得--padding-when-small
未定义。因此
padding: var(--padding-when-small, var(--padding-when-large));
可以正常工作。computedStyleMap 和 不支持类型化 CSSOM 的浏览器的 getComputedStyle 是调试 CSS 属性的非常好的帮手。
还有一点。如果你要将这个技巧与动画结合使用?你可以说,创建一个类似于开关的效果,其中属性根据分配给属性的数字开始变化。
查看 CodePen 上 Rock Starwind 的 使用空间切换和暂停/负延迟动画来创建数值选项。
(@RockStarwind) 在 CodePen 上。
! ^ 这是下一级的空间切换用法。他们本质上是在纯 CSS 中创建了一个可索引的数组。
任何想进行更高级的无脚本 CSS 游戏编程(为了挑战乐趣)的人可能都希望学习 @RockStarwind 在这里发明的技术!
它在 Firefox 中不起作用。显然,暂停动画意味着 FF 从未分配初始值。
Lea Verou 几周前发布了一篇关于这方面的文章。
https://lea.verou.me/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/
她确实这样做了,这就是为什么我在文章中讨论了它,并包含了她的演示和来自她的引用。
嗨,Chris Coyier。
感谢您鼓舞人心的文章。
你误解了
--foo: initial;
。关键字
initial
在这种情况下也将属性设置为其初始值。W3C 表示
Carter Li 已经 描述了
--mq-sm: initial;
的效果很聪明。但由于这是一个技巧,它实际上读起来很令人困惑,在大型项目中,这会成为调试/维护的噩梦。可读性应该是长期项目中最重要的优先事项。也许预/后处理器可以以更优雅的方式利用它?
我对这个很着迷,但是,我读了几遍这篇文章,到目前为止,无论我尝试使用什么 **空间切换**,我都无法实现任何我无法通过 **@media 查询** 更简单地实现的效果。
以下是根据顶部的 **padding** 示例得出的结论
而且…这有效。
但它与以下代码完全相同
现在,很明显我遗漏了一些东西(毫无疑问,一旦我最终理解了它,它就会显得显而易见),但是 **空间切换** 究竟启用了什么,而这不仅仅是将 **@media 查询** 值插入到 *不包含媒体查询的* 样式声明中的聪明方法?
如果你正在寻找一些会永远彻底改变 CSS 工作方式的东西,我认为你不会在这里找到它。它是一个(等等)CSS 技巧。
但我确实认为能够在 *一个* 自定义属性发生变化时设置 *多个* 属性非常棒。当然,这可能是媒体查询式的,但它可能是其他东西,比如 JavaScript 改变了它,或者
:checked
状态改变。如果没有这种感觉像是黑客的方法,它会更加酷。
使用额外的变量,你可以做到这一点。例如,使用
--dark
和--light
开关,其中一个为initial
,另一个为一个空格。然后使用background-color: var(--light, white) var(--dark, black);
。一个将解析为它的回退值,另一个将解析为空格,因此被忽略。请查看下面的代码示例。