CSS条件变量

2017-01-05 11:04:38来源:作者:W3CPlus人点击

我将从这里说起: 在W3C标准规范中,使用 CSS变量 并没有任何条件。我认为这是规范文档中的一个很大的缺陷,CSS变量已经实现了很多我们之前无法实现的功能,并且我们未来可能大量使用,缺失文档描述着实让人沮丧。

但是如果现在我们需要这些对CSS变量的描述怎么办?好,借助其他的CSS知识,我们可以在一些实例中略窥一二。

问题定义

我们需要的是使用一个单一的CSS变量来给不同的CSS属性设置不同的值。这些值并不会基于CSS变量(因为这些值并不会由我们的变量计算得出)。我们需要的是条件。

使用二进制计算

长话短说, 我先列出一些解决策略,然后再详诉。

:root { --is-big: 0;}.is-big { --is-big: 1;}.block { padding: calc( 25px * var(--is-big) + 10px * (1 - var(--is-big)) ); border-width: calc( 3px * var(--is-big) + 1px * (1 - var(--is-big)) );}

在这个例子中,我们给所有 .block 元素设置 10px 内边距( padding )和 1px ( border-width ) 的边框,条件是作用在那些元素上的 --is-big 变量不为 1 ,否则,元素内边距和边框分别为 25px 和 3px 。

CSS变量机制也很简单: 我们在 calc() 使用了一个CSS变量的两个可能值, --is-big 要么为 0 ,要么为 1 。换句话说, .block 的内边距要么为 25px * 1 + 10px * 0 ( 25px ) 要么为 25px * 0 + 10px * 1 ( 10px )。

更复杂的情况

我们可以用这种方法去选择CSS变量可能超过 2 个值。相应地,每增加一个可能值,表达式也就变的越复杂。选择 3 个值进看起来像这样。

.block { padding: calc( 100px * (1 - var(--foo)) * (2 - var(--foo)) * 0.5 + 20px * var(--foo) * (2 - var(--foo)) + 3px * var(--foo) * (1 - var(--foo)) * -0.5 );}

--foo 的值可能是 0 , 1 或者 2 。 padding 的值也相应的可能为 100px , 200px 或者 3px 。

原理是相通的: 我们只需要将这个表达式和每一个可能值相乘就好了,结果为 1 的是我们需要的,其他则为 0 。组成这个表达式也很简单。我们只需要使另外的可能值生效就好了。设计好之后,我们需要添加一个触发值以此来调整结果以致于它等于 1 ,就是这样了。

可能存在的陷阱

With the increasing complexity of such calculations, there is a chance at one point they would stop from working. Why? There is this note in specs:

UAs must support calc() expressions of at least 20 terms, where each NUMBER, DIMENSION, or PERCENTAGE is a term. If a calc() expression contains more than the supported number of terms, it must be treated as if it were invalid.

Of course, I tested this a bit and couldn't found such limitations in the browsers I tested, but there is still a chance either you would write some really complex code that would meet the possible existing limit, or some of the browsers could introduce this limit in the future, so be careful when using really complex calculations.

随着计算式越来越复杂,有可能会出错。为什么? 规范中 是这样描述的:

UAs 支持至少 20 种 calc() 表达式,number,dimension 或者percentage 都是表达式。如果 calc() 包含了它所支持以外的表达式,它会把这些表达式视作无效。

当然,我自己在浏览器测试了一下,并没有这样的问题。但是你写了非常复杂的代码仍然有这样的可能。或者在将来某些浏览器会有这样的问题,因此当使用非常复杂的计算式时,请小心为上。

根据条件获取颜色

正如你看到的,这些计算表达式只能用在那些可计算的属性上面,因此我们无法使用它改变 display 的值或者计算一些非数字的属性。但是,对于颜色( color )呢?事实上,我们能计算颜色的各个组成部分。不幸的是,现在只有webkit和blink 浏览器引擎能支持颜色的计算,因为 火狐 现在还不支持在 calc() 里使用 rgba() 和其他颜色函数。 但是当浏览器支持的时候(或者你在支持颜色计算的浏览器上想体验一下),我们可以这样做。

:root { --is-red: 0;}.block { background: rgba( calc( 255*var(--is-red) + 0*(1 - var(--is-red)) ), calc( 0*var(--is-red) + 255*(1 - var(--is-red)) ), 0, 1);}

这里我们把 lime 颜色设置成默认值,如果 --is-red 被设置成 1 ,那么得到的颜色就会变成 red ( 注意: 如果我们完全忽略CSS表达式,颜色的各个部分则为 0 ,这使我们的代码更简洁,在这里,我尽量让CSS变量计算变得更清楚)。

借此你对于可计算的属性可以做任何运算,甚至可能创造出任何颜色(包含渐变?你可以试试)。

规则中另一个陷阱

当我们在测试如何产生我们想要的颜色时,我发现了在规范文档中存在一个非常奇怪的现象,它被叫做" 类型检查 "。我现在很讨厌它。意味着属性值只接受整数值( <integer> ),如果你在 calc() 里存在除法运算或一些非整数,即便结果是整数,"处理后的类型"也不会是整数,它可能是数字,意味着我们无法给属性设置我们写入的值。当这些计算式包含两个可能值的时候,我们需要一个非整数指示器。当我们使用颜色或者其他只允许整数的属性时(像 z-index )时能够使表达式无效。比如:

calc(255 * (1 - var(--bar)) * (var(--bar) - 2) * -0.5)

当 rgba() 里时使用这个 calc() 是无效的。最开始的时候我认为这是个bug,尤其是知道 rgba() 可以接受超过 255 的值( rgba(9001, +9001, -9001, 43) 得到的是一个正常的黄色( yellow )),但是这种类型检查似乎对于浏览器来说太难了(因为乘了个 0.5 ,非整数)。

能否解决?

这里有一种远不完美的解决策略。因为我们知道我们想要的值和非整数指示器,我们只要先计算出来然后四舍五入一下。嗯,这意味着结果在某些时候可能不太精确,但总好过什么都没有吧。

但是这里还有一种策略可以应对颜色,我们可以使用 hsla ,而不是 rgba ,因为 hsla 不接受非整数,而是接受数字和百分比。因此在类型检查中不存在冲突。但是对于特定的属性例如 z-index ,还是没辙。因为即便这样,如果从 rgb 转到 hsl 还是会存在精度的损失。但是这相对于前面的方法还是损失小些。

预处理

当存在分支时,我们仍然可以手写CSS。但是当我们遇到非常复杂的条件时,或者当我们要拿到颜色时,我们最好使用工具这样可以让书写变的更方便。幸运的是,我们正有这样的预处理器。

这是stylus里如何书写的例子:

conditional($var, $values...) $result = '' // If there is only an array passed, use its contents if length($values) == 1 $values = $values[0] // Validating the values and check if we need to do anything at all $type = null $equal = true for $value, $i in $values if $i > 0 and $value != $values[0] $equal = false $value_type = typeof($value) $type = $type || $value_type if !($type == 'unit' or $type == 'rgba') error('Conditional function can accept only numbers or colors') if $type != $value_type error('Conditional function can accept only same type values') // If all the values are equal, just return one of them if $equal return $values[0] // Handling numbers if $type == 'unit' $result = 'calc(' $i_count = 0 for $value, $i in $values $multiplier = '' $modifier = 1 $j_count = 0 for $j in 0..(length($values) - 1) if $j != $i $j_count = $j_count + 1 // We could use just the general multiplier, // but for 0 and 1 we can simplify it a bit. if $j == 0 $modifier = $modifier * $i $multiplier = $multiplier + $var else if $j == 1 $modifier = $modifier * ($j - $i) $multiplier = $multiplier + '(1 - ' + $var + ')' else $modifier = $modifier * ($i - $j) $multiplier = $multiplier + '(' + $var + ' - ' + $j + ')' if $j_count < length($values) - 1 $multiplier = $multiplier + ' * ' // If value is zero, just don't add it there lol if $value != 0 if $modifier != 1 $multiplier = $multiplier + ' * ' + (1 / $modifier) $result = $result + ($i_count > 0 ? ' + ' : '') + $value + ' * ' + $multiplier $i_count = $i_count + 1 $result = $result + ')' // Handling colors if $type == 'rgba' $hues = () $saturations = () $lightnesses = () $alphas = () for $value in $values push($hues, unit(hue($value), '')) push($saturations, saturation($value)) push($lightnesses, lightness($value)) push($alphas, alpha($value)) $result = 'hsla(' + conditional($var, $hues) + ', ' + conditional($var, $saturations) + ', ' + conditional($var, $lightnesses) + ', ' + conditional($var, $alphas) + ')' return unquote($result)

是的,这里代码比较多,但是这个mixin可以在多个值的条件下产生number 和 colors 的色值。

使用也很简单:

border-width: conditional(var(--foo), 10px, 20px)

第一个参数变量是我们的CSS变量,第二个参数变量是当CSS变量为 0 时被应用,第三个参数变量是当CSS变量为 1 时被应用。

上面的调用会产生一个适当的条件:

border-width: calc(10px * (1 - var(--foo)) + 20px * var(--foo));

这里有一个更复杂的示例:

color: conditional(var(--bar), red, lime, rebeccapurple, orange)

可能生成你不想手写的代码:

color: hsla(calc(120 * var(--bar) * (var(--bar) - 2) * (var(--bar) - 3) * 0.5 + 270 * var(--bar) * (1 - var(--bar)) * (var(--bar) - 3) * 0.5 + 38.82352941176471 * var(--bar) * (1 - var(--bar)) * (var(--bar) - 2) * -0.16666666666666666), calc(100% * (1 - var(--bar)) * (var(--bar) - 2) * (var(--bar) - 3) * 0.16666666666666666 + 100% * var(--bar) * (var(--bar) - 2) * (var(--bar) - 3) * 0.5 + 49.99999999999999% * var(--bar) * (1 - var(--bar)) * (var(--bar) - 3) * 0.5 + 100% * var(--bar) * (1 - var(--bar)) * (var(--bar) - 2) * -0.16666666666666666), calc(50% * (1 - var(--bar)) * (var(--bar) - 2) * (var(--bar) - 3) * 0.16666666666666666 + 50% * var(--bar) * (var(--bar) - 2) * (var(--bar) - 3) * 0.5 + 40% * var(--bar) * (1 - var(--bar)) * (var(--bar) - 3) * 0.5 + 50% * var(--bar) * (1 - var(--bar)) * (var(--bar) - 2) * -0.16666666666666666), 1);

注意这里没有整数监测的属性,因此这里对于 z-index 等属性不会生效。但是它已经将颜色转化成了 hsla ,使它们变的有效(尽管只有这种转换只有我们需要的时候,它才有所优势)。另外在这个mixin中我们无法执行的是使用CSS变量作为属性值。这对于非整数的属性也是可以实现的,因为我们的条件表达式中是可以插入CSS变量的。也许,等我有空了,我会让这个mixin接受CSS变量。目前,用本文解释的算法也是可能实现的。

降级

当然,如果你打算真的使用它,你需要使用一个方式来降级。这很容易应对浏览器不支持CSS变量的时候:你可以在条件申明前使用回退值。

.block { padding: 100px; /* fallback */ padding: calc( 100px * ((1 - var(--foo)) * (2 - var(--foo)) / 2) + 20px * (var(--foo) * (2 - var(--foo))) + 3px * (var(--foo) * (1 - var(--foo)) / -2) );}

但是当涉及到颜色时,我们会遇到一个问题:当浏览器支持CSS变量时,任何包含CSS变量的申明都会被认为有效。这意味着在CSS中,我们是不太可能在包含CSS变量的申明中做回退的。

在有效的CSS中, background 会得到一个初始值( initial ),不是在回退中我们提供的。因此我们需要提供在一些场景中使用降级处理——使用 @support 来测试除了有效的CSS变量其他值。

在我们的例子中,火狐浏览器中我们需要像这样处理:

.block { color: #f00;}@supports (color: rgb(0, calc(0), 0)) { .block { color: rgba(calc(255 * (1 - var(--foo))), calc(255 * var(--foo)), 0, 1); }}

这里我们测试一下在 color 函数中使用 calc() 并且做为 color 的值。

这里也可能自动创建回退,但这里我并不推荐你使用预处理器处理回退,因为创建这些东西的复杂度超越了CSS预处理器的能力。

用例

我真的不喜欢为那种一目了然的东西提供使用场景。这里我就简单提一下吧。这里不仅仅是CSS变量的,还有一些生成条件,像 calc() 的结果;

CSS变量应用在那些主题分明的块上是绝佳的。你可能有很多的块,然后要把主题应用在这些块上,你只需要一个像 --block--variant:1 的CSS变量。当我们在不同的主题中想给不同的属性设置不同的值时,CSS变量可能是最好的做法了,否则的话,你可能需要给每一种主题制定一个变量。 排版。如果我们在CSS变量中使用 < , <= , > , >= 等判断条件,那么可能会应用不同的规则采用不同同的字体,因此你可以设置不同的行高( line-height ),字体粗细( font-weight )和基于字号( font-size )的其他属性。 响应式设计。如果对表达式设置以条件,这不就变成媒体查询了嘛。你可以使用 vw 或百分比,决定具体条件下,使用哪个样式。

当然也会有其他的应用场景,发现后一定要告诉我。我很确信还有别的东西,但是我记性不太好,记不住所有曾经我想用CSS做的东西。

未来特性

我真的很希望在CSS规范文档中有具体的描述,而不用依赖calc hack 或使用一些特殊值来应对不能计算的属性值。现在我们不可能有除了严格相等其他之外的条件,因此没有"变量大于 x "或者其他之类的。我不知道为什么我们我能使用这些条件,如果你认识文档开发商,帮我给他们提点意见。我希望他们别以“用JS”或者为什么这样不可能的理由来搪塞我。

本文根据 @kizmarh 的《 Conditions for CSS Variables 》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处: http://kizu.ru/en/fun/conditions-for-css-variables/ 。

Shadow Walker

一名在校研究生,热爱前端,热爱英语,热爱摄影,喜欢一切能表达情绪的东西,喜欢黑色,喜欢秋天,喜欢孤独。。

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台