除了 `@media` 查询的特定性质和一些使用 CSS 自定义属性的 高级技巧 之外,CSS 还没有 `switch` 规则或条件 `if`。 让我们看看如果我们有了它为什么有用,并看看一个今天就可以使用的技巧来实现它。
关于可能性的最新讨论
虽然这些东西在今天都不能使用,但在过去的一年中,关于通用条件 CSS 概念的讨论还是不少的。
- Brian Kardell 提出了一个 `switch()` 语句,而 Tab Atkins 对此进行了讨论。
- Jonathan Neal 提出了一个媒体查询变体,用于条件值,引发了大量的讨论。
- Lea Verou 提出了“更高级别的自定义属性”(以下是 Bramus Van Damme 对它们的看法),这看起来非常有用。
所以,是的。对条件 CSS 的需求是存在的。
想象一下条件 CSS 为什么有用
也许是在滚动一定距离后进行视觉更改。 数值输入在一定范围内后的视觉更改。 具有少量状态的组件。
有一整类非常流行的用于 UI 的 JavaScript 库(例如 React、Vue 等),它们本质上是用于基于状态构建 UI 的。 显然,这是开发人员的需求。 如果我们可以将基于状态的样式移动到 CSS,那么我们可能需要更少的 JavaScript——并且可能会有更好的关注点分离。
一个共同的主题
我们已经在 CSS 中拥有了自定义属性,我们可以基于它们来构建状态更改逻辑,并将样式块的更改作为自定义属性更改为特定值的副作用。
确实,我们已经有了更改样式块的机制。 我们可以通过 JavaScript 更改 `class`,并且该 `class` 可以应用我们想要的任何 CSS。 但这并不意味着基于状态的 CSS 样式没有用。 我们并不总是能够或可能不想为此编写任何 JavaScript,而是以其他方式更改自定义属性(例如媒体查询、HTML 更改等)。 在 CSS 中执行此操作意味着有助于分离业务逻辑和视觉样式逻辑。
一个技巧!使用 `@keyframes` 进行状态切换
CSS `@keyframes` 可用于 `switch` 特定更改。 通过 `animation` 属性的力量,可以打开一个选择要显示的确切帧的可能性,并使其精确地暂停在该帧上,有效地模拟 switch-case 语句或基于状态的样式。
让我们通过使用 `animation-delay` 属性来看一下它的实际效果。
以下是该 Pen 中发生的情况
- `animation-delay`:负延迟值 强制特定帧(或帧之间)生效(正值不起作用)。 我们将使用此技巧来强制状态。
- `animation-play-state: paused`:我们实际上并没有动画任何东西,所以动画将保持暂停状态。
- `animation-duration`:实际持续时间无关紧要,它只需要一个,以便存在一个时间跨度来保存不同的关键帧。 我们将使其成为一个像 `100.001s` 这样的值,以便如果我们延迟 `100s`,最后一个关键帧仍然有效。 持续时间需要长于延迟值。
第一个范围输入将 `animation-delay` 修改为 `-100s` 和 `0s` 之间的范围。
一个现实世界的用例
在我们直接跳入工作示例之前,值得更详细地讨论一下这个技巧,因为有一些细微差别需要注意。
首先,该技巧仅适用于数值。 因此,颜色值或字符串,因为它严格执行数学运算。
其次,有布尔技巧。 考虑一个变量 `--value: 10`,它可以取 0 到 100 之间的任何数值。 如果值大于 5,我们想要应用颜色。 我们如何知道值是大于还是小于 5? 即使我们知道,这如何帮助我们呢?
--is-above-5: clamp(0, var(--value) - 5, 1)
--value | --is-above-5 | 结果 |
---|---|---|
0 | 0 | 0 – 5 = -5,`clamp()` 强制值为不小于 0 |
2 | 0 | 2 – 5 = -3,`clamp()` 强制值为不小于 0 |
5 | 0 | 5 – 5 = 0 |
7 | 1 | 7 – 5 = 2,`clamp()` 强制值为不大于 1 |
`clamp()` 就像一个更智能的 `calc()`,因为它允许我们在声明理想值的同时将计算出的值严格限制在范围内。 该范围是实现布尔变量所需的一切。
在 `clamp()` 的第二个参数中编写任何数学运算,这将输出 0(或以下)或 1(或以上)。 确保不要编写任何可能导致 0 到 1 之间数字的数学运算。
以下是它的工作原理
范围输入的唯一作用是通过为 `--value`、`--min` 和 `--max` 定义值来“广播”其值,然后使用 `oninput` 事件修改 `--value`。 这是在 CSS 中获得类似状态行为的最简单方法。 不需要 JavaScript。
使用 CSS 数学函数,可以从相同的变量推断出进度条的“完成”百分比。
--completed: calc((var(--value) - var(--min) ) / (var(--max) - var(--min)) * 100);
现在,我们知道值是否超过某个百分比,这给了我们另一种根据状态进行更改的方法。
--over-30: clamp(0, var(--completed) - 30, 1);
--over-70: clamp(0, var(--completed) - 70, 1);
/* ...and so on... */
好的,很好,但是我们如何使用它来选择特定的关键帧呢? 通过使用 `max()` 函数。
--frame: max(
calc(1 - var(--over-30)),
var(--over-30) * 2,
var(--over-70) * 3,
var(--is-100) * 4
);
CSS 布尔值的问题在于,有很多方法可以使用它们来实现某个目标,并且必须发挥创造力,找到一个简短易读的公式。
在上面的公式中,如果布尔值为 `1`,则布尔值将“切换”帧号。 由于我们使用的是 `max` 函数,因此最大的切换帧号将是 `--frame` 的计算值。
请注意,颜色更改有一个轻微的过渡。 我们可以在填充区域使用 `background: currentColor;` 来实现这一点,它继承自父元素的颜色,但我选择使用 CSS Houdini 来说明通过声明其 `type` 将过渡分配给 CSS 变量的功能。
以下 Pen 中展示了一个被广泛使用的 **CSS 布尔技巧** 的示例,它是一个纯 CSS 组件,具有许多变量,允许广泛的自定义。
我相信这个小技巧还有很多其他用例,并且很高兴看到社区的创造力还能实现什么。
然而,oninput 处理程序是 JS,而且是最糟糕的 eval 类型。
如果我们在 CSS 中有条件语句和对某些 DOM 属性的访问,那将非常有用,但另一方面,就像发现奇怪的内联事件处理程序令人恼火一样,想象一下尝试管理一个逻辑可以同时存在于 JS、HTML 和 CSS 中的代码库。 我们将需要更高级的 CSS linter;p
@MS – 它只是一个我个人使用的用例示例。 我认为对应该工作并被视为黑盒的简单组件使用内联事件绝对没有问题,其他开发人员甚至不应该关心发生了什么。
可以把它想象成一个Web组件,这是黑盒的终极例子,你根本不需要知道也不需要关心它是如何工作的,你只关心它是否能满足你的需求。
无论如何,上面描述的方法适用于非常复杂的场景。
假设你已经有一个组件可以进行一些计算,并且你想根据某些参数将这些计算“转换”成不同的CSS样式。例如,一个滚动事件监听器,使用scrollTop值,你可以简单地将该值提供给CSS,现在CSS突然可以访问动态数据,这为许多可能性打开了大门,就像文章中描述的那样。
你可以用它做很多事情,我还没有想到全部。我只是发明了一个工具,现在就看人们如何找到使用它的方法了。
就像一个黑客一样,我坐了数年,差不多20年——坐着思考——我怎样才能操纵CSS来做它的创建者从未想过的事情?我想把CSS操纵到极致,让它屈服于我的意志,迫使它达到我所能掌握的语言和想象力的极限,而想象力是找到这些CSS技巧的关键。
非常感谢您不断突破极限。
我看到了这里在简化配色方案管理方面的巨大潜力。无需每次调用配色方案时都设置数十个属性,你只需在一行代码中引用配色方案动画中的一个帧即可:https://codepen.io/jproffitt71/pen/bGBMVJx
这让我不禁思考,CSS是否应该有类似于@properties的关键字来定义自定义属性组。
啊,我看到你在开头就提到了这个提案:https://github.com/w3c/csswg-drafts/issues/5624
抱歉。不过这确实非常令人兴奋。