最近,CSS 添加了许多很酷的新特性,例如 自定义属性 和新的 函数。虽然这些东西可以使我们的生活变得更容易,但它们也可能以有趣的方式与预处理器(如 Sass)发生交互。
因此,这将是一篇关于我遇到的问题、我如何解决这些问题以及为什么我今天仍然认为 Sass 是必要的文章。
错误
如果您使用过新的 min()
和 max()
函数,当使用不同的单位时,您可能会遇到类似这样的错误消息:“不兼容的单位:vh
和 em
。”

min()
/ max()
函数中使用不同类型的单位时发生的错误这是因为 **Sass 有自己的 min()
函数,并且忽略了 CSS min()
函数**。此外,Sass 无法使用两个单位之间没有固定关系的值进行任何类型的计算。
例如,cm
和 in
单位之间存在固定关系,因此 Sass 可以计算出 min(20in, 50cm)
的结果,并且在我们在代码中尝试使用它时不会抛出错误。
其他单位也是如此。例如,角度单位之间都存在固定关系:1turn
、1rad
或 1grad
始终计算为相同的 deg
值。同样,1s
始终为 1000ms
,1kHz
始终为 1000Hz
,1dppx
始终为 96dpi
,1in
始终为 96px
。这就是为什么 Sass 可以 在它们之间进行转换 并在计算和函数(如它自己的 min()
函数)中混合它们。
但是当这些单位之间没有固定关系时(例如前面 em
和 vh
单位的情况),就会出现问题。
而且不仅仅是不同的单位。尝试在 min()
中使用 calc()
也会导致错误。如果我尝试类似 calc(20em + 7px)
的操作,我得到的错误是,“calc(20em + 7px)
不是 min
的数字。”

min()
函数中嵌套使用 calc()
时,使用不同单位值发生的错误当我们想要在 CSS 滤镜(如 invert()
)中使用 CSS 变量或数学 CSS 函数(如 calc()
、min()
或 max()
)的结果时,就会出现另一个问题。
在这种情况下,我们会收到提示:“$color: 'var(--p, 0.85)
不是 invert
的颜色。”

var()
在 filter: invert()
中发生的错误grayscale()
也会发生同样的情况:“$color
: ‘calc(.2 + var(--d, .3))
‘ 不是 grayscale
的颜色。”

calc()
在 filter: grayscale()
中发生的错误opacity()
也会导致同样的问题:“$color
: ‘var(--p, 0.8)
‘ 不是 opacity
的颜色。”

var()
在 filter: opacity()
中发生的错误但是,其他 filter
函数——包括 sepia()
、blur()
、drop-shadow()
、brightness()
、contrast()
和 hue-rotate()
——都可以与 CSS 变量一起正常工作!
事实证明,发生的情况与 min()
和 max()
问题类似。Sass 没有内置的 sepia()
、blur()
、drop-shadow()
、brightness()
、contrast()
、hue-rotate()
函数,但它确实有自己的 grayscale()
、invert()
和 opacity()
函数,并且它们的第一个参数是 $color
值。由于它没有找到该参数,因此会抛出错误。
出于同样的原因,当我们尝试使用列出至少两个 hsl()
或 hsla()
值的 CSS 变量时,也会遇到麻烦。

var()
在 color: hsl()
中发生的错误。另一方面,color: hsl(9, var(--sl, 95%, 65%))
是完全有效的 CSS,并且在没有 Sass 的情况下也能正常工作。
rgb()
和 rgba()
函数也会发生完全相同的事情。

var()
在 color: rgba()
中发生的错误。此外,如果我们导入 Compass 并尝试在 linear-gradient()
或 radial-gradient()
中使用 CSS 变量,我们会得到另一个错误,即使在 conic-gradient()
中使用变量也能正常工作(也就是说,如果浏览器支持它)。

var()
在 background: linear-gradient()
中发生的错误。这是因为 Compass 带有 linear-gradient()
和 radial-gradient()
函数,但从未添加过 conic-gradient()
函数。
在所有这些情况下,问题都源于 Sass 或 Compass 具有同名的函数,并假设这些是我们打算在代码中使用的函数。
哎呀!
解决方案
这里的技巧是记住 **Sass 区分大小写,但 CSS 不区分大小写。**
这意味着我们可以编写 Min(20em, 50vh)
,Sass 不会将其识别为它自己的 min()
函数。不会抛出错误,并且它仍然是有效的 CSS,可以按预期工作。类似地,编写 HSL()
/ HSLA()
/ RGB()
/ RGBA()
或 Invert()
可以让我们避免之前遇到的问题。
至于渐变,我通常更喜欢 linear-Gradient()
和 radial-Gradient()
,因为它更接近 SVG 版本,但在其中使用至少一个大写字母也可以正常工作。
但是为什么?
几乎每次我在 Twitter 上发布任何与 Sass 相关的内容时,都会有人告诉我现在我们有了 CSS 变量,就不应该使用 Sass 了。我想解决这个问题并解释为什么我不同意。
首先,虽然我发现 CSS 变量非常有用,并且在过去三年中几乎在所有地方都使用了它们,但需要注意的是,它们会带来性能成本,并且在 calc()
计算的迷宫中追踪错误的来源,使用我们当前的 DevTools 可能会很痛苦。我尽量避免过度使用它们,以免陷入使用它们的缺点大于好处的境地。

calc()
表达式的结果是什么。一般来说,如果它像常量一样工作,不会因元素或状态而改变(在这种情况下,自定义属性绝对是 最佳选择)或减少编译后的 CSS 的数量(解决前缀带来的重复问题),那么我就会使用 Sass 变量。
其次,变量一直是我使用 Sass 的原因中很小的一部分。当我从 2012 年底开始使用 Sass 时,主要是因为循环,这是 CSS 中仍然没有的功能。虽然我已经将部分循环转移到 HTML 预处理器(因为它减少了生成的代码,并且避免了以后需要同时修改 HTML 和 CSS),但我仍然在很多情况下使用 Sass 循环,例如生成值的列表、渐变函数中的停止列表、多边形函数中的点列表、变换列表等等。
举个例子。我以前使用预处理器生成 n
个 HTML 项目。预处理器的选择关系不大,但这里我将使用 Pug。
- let n = 12;
while n--
.item
然后,我将 $n
变量设置到 Sass 中(它必须与 HTML 中的变量相等),并循环到该变量以生成定位每个项目的变换
$n: 12;
$ba: 360deg/$n;
$d: 2em;
.item {
position: absolute;
top: 50%; left: 50%;
margin: -.5*$d;
width: $d; height: $d;
/* prettifying styles */
@for $i from 0 to $n {
&:nth-child(#{$i + 1}) {
transform: rotate($i*$ba) translate(2*$d) rotate(-$i*$ba);
&::before { content: '#{$i}' }
}
}
}
但是,这意味着在更改项目数量时,我必须同时更改 Pug 和 Sass,这使得生成的代码非常重复。

我现在改为让 Pug 生成索引作为自定义属性,然后在 transform
声明中使用这些属性。
- let n = 12;
body(style=`--n: ${n}`)
- for(let i = 0; i < n; i++)
.item(style=`--i: ${i}`)
$d: 2em;
.item {
position: absolute;
top: 50%;
left: 50%;
margin: -.5*$d;
width: $d;
height: $d;
/* prettifying styles */
--az: calc(var(--i)*1turn/var(--n));
transform: rotate(var(--az)) translate(2*$d) rotate(calc(-1*var(--az)));
counter-reset: i var(--i);
&::before { content: counter(i) }
}
这大大减少了生成的代码。

但是,如果我想生成彩虹之类的效果,仍然需要在 Sass 中使用循环。
@function get-rainbow($n: 12, $sat: 90%, $lum: 65%) {
$unit: 360/$n;
$s-list: ();
@for $i from 0 through $n {
$s-list: $s-list, hsl($i*$unit, $sat, $lum)
}
@return $s-list
}
html { background: linear-gradient(90deg, get-rainbow()) }
当然,我可以从 Pug 生成它作为列表变量,但这并没有利用 CSS 变量的动态特性,也没有减少发送到浏览器的代码量,因此没有任何好处。
我使用 Sass(和 Compass)的另一个重要部分与内置的数学函数(如三角函数)相关,这些函数现在是 CSS 规范的一部分,但尚未在任何浏览器中实现。Sass 也没有这些函数,但 Compass 有,这就是为什么我经常需要使用 Compass 的原因。
当然,我可以在 Sass 中编写自己的这些函数。在 Compass 支持反三角函数之前,我确实求助于此。我真的很需要它们,所以我根据 泰勒级数 编写了自己的函数。但 Compass 现在提供了这些类型的函数,它们比我的函数更好,性能也更高。
数学函数对我来说极其重要,因为我是一名技术人员,而不是艺术家。我的 CSS 中的值通常是数学计算的结果。它们不是魔法数字,也不是纯粹为了美观而使用的数字。一个例子是生成创建规则或准规则多边形的剪辑路径点的列表。想想我们需要创建非矩形头像或贴纸的情况。
让我们考虑一个正多边形,其顶点位于一个圆上,该圆的半径是我们开始的正方形元素的50%
。在以下演示中拖动滑块,我们可以看到对于不同数量的顶点,这些点是如何放置的。
将其转换为Sass代码,我们得到
@mixin reg-poly($n: 3) {
$ba: 360deg/$n; // base angle
$p: (); // point coords list, initially empty
@for $i from 0 to $n {
$ca: $i*$ba; // current angle
$x: 50%*(1 + cos($ca)); // x coord of current point
$y: 50%*(1 + sin($ca)); // y coord of current point
$p: $p, $x $y // add current point coords to point coords list
}
clip-path: polygon($p) // set clip-path to list of points
}
请注意,在这里我们也使用了循环以及条件语句和取模运算,这些在不使用Sass的情况下使用CSS时非常麻烦。
这个代码的稍微更高级的版本可能涉及到通过向每个顶点的角度添加相同的偏移角($oa
)来旋转多边形。这可以在以下演示中看到。此示例中加入了一个星形混合宏,其工作方式类似,只是我们始终具有偶数个顶点,并且每个奇数索引的顶点都位于较小半径($f*50%
,其中$f
小于1)的圆上。
我们也可以得到像这样的圆润的星星
或者带有有趣border
图案的贴纸。在这个特定的演示中,每个贴纸都是用一个HTML元素创建的,border
图案是用Sass中的clip-path
、循环和数学运算创建的。事实上,其中包含相当多的数学运算。
另一个例子是这些卡片背景,其中循环、取模运算和指数函数协同工作以生成抖动像素背景层。
此演示碰巧也大量依赖于CSS变量。
然后还有使用混合宏来避免在为诸如范围输入之类的元素设置样式时一遍又一遍地编写完全相同的声明。不同的浏览器使用不同的伪元素来设置此类控件的组件样式,因此对于每个组件,我们都必须设置控制其外观的多个伪元素的样式。
遗憾的是,尽管将此放入我们的CSS中可能很诱人
input::-webkit-slider-runnable-track,
input::-moz-range-track,
input::-ms-track { /* common styles */ }
…但我们无法做到这一点,因为它不起作用!如果即使其中一个选择器不被识别,整个规则集也会被丢弃。由于没有任何浏览器识别以上所有三个选择器,因此样式在任何浏览器中都不会应用。
如果我们希望应用我们的样式,则需要类似这样的内容
input::-webkit-slider-runnable-track { /* common styles */ }
input::-moz-range-track { /* common styles */ }
input::-ms-track { /* common styles */ }
但这可能意味着许多相同的样式重复三次。如果我们想更改轨道background
,我们需要在::-webkit-slider-runnable-track
样式、::-moz-range-track
样式和::-ms-track
样式中更改它。
我们拥有的唯一合理的解决方案是使用混合宏。样式在编译后的代码中重复,因为它们必须在那里重复,但我们不再需要三次编写相同的内容了。
@mixin track() { /* common styles */ }
input {
&::-webkit-slider-runnable-track { @include track }
&::-moz-range-track { @include track }
&::-ms-track { @include track }
}
底线是:是的,在2020年,Sass仍然非常必要。
精彩的文章。使很多事情都变得清晰。我发现css和sass变量不能很好地混合。关于大小写敏感性的观点真的让我大开眼界。谢谢!!你是一位有天赋的css程序员。
嗨,Ana,
您可以通过使用最新版本的Dart Sass来避免其中的一些问题。我强烈建议此时关闭Node/LibSass。在过去的一年中,我们一直在对Sass进行重大改进,但LibSass和NodeSass项目在这些更新方面落后了。
这将解决您所有的颜色函数问题,以及您提到的
min()
和max()
示例。仍然存在一些剩余的冲突,就像您上面提到的那样。我们正在努力尽快解决它们。如果您使用的是Dart Sass(该语言的官方核心实现),您将更快地获得这些更新。Dart Sass还具有强大的模块系统,这将帮助我们避免将来出现这些问题。
顺便说一句,使用最新的DartSass是一个CodePen上的活跃项目!它还没有上线,但应该不会太久了。
在Dart Sass 1.26.9中,对于Ana提到的内容,我仍然遇到min()、max()和clamp()的“不兼容单位”编译错误…
font-size: clamp(1em, .5rem + 1vw, 2em);
width: min(960px, 90%);
我解决它的方法是将这些值包含在calc()函数中…
font-size: calc(clamp(1em, .5rem + 1vw, 2em));
width: calc(min(960px, 90%));
对于遇到此类问题的其他人,此方法现在可以解决问题,它是有效的原生CSS,并且(我希望)在Sass更新以处理这些更新的CSS函数时仍然可以正常工作——如果在您阅读本文时尚未更新的话!
嘿,Greg!
我遇到了这个问题。这是因为声明值内的计算。好消息是正在跟踪修复此问题
https://github.com/sass/sass/issues/2860
我通过将声明的值放在CSS自定义属性中并以这种方式调用它,取得了一些成功。
这也是一个好主意,Stuart!我现在将坚持使用calc()函数,但我喜欢那个方法。
很高兴clamp()的修复即将到来。
您还可以通过转义它们来转义任何冲突的函数,例如
#{'min(50%, 3rem)'}
,这正是我最近使用min()时一直在做的事情。是的,我刚刚想到这一点……转义是否更类似于级联,因为您让下一个合适的程序(在本例中为浏览器的CSS解释器)来解决问题?如果我在代码中看到Min()和min(),在阅读本文之前,它不会向我传达正在发生的事情。转义SASS以便CSS可以完成工作,并将向我传达正在发生的事情。如果只有您一个人编写代码,则使用大小写来解决问题会起作用,但对于团队来说,使用大小写是不可行的!
更改函数的大小写会在我看来造成一些噪音,但这是一个有趣且简单的解决方法。
我做出了另一个选择:使用自定义的(作为包提供)覆盖SASS
hsl
函数。除了使用大小写或在较新的SASS中使用模块来解决min()等之间的歧义之外,另一种方法是让SASS将此类函数视为优化机会,如果它可以评估它们,否则保留它们。在我看来,这将是使用SASS的真正原因。否则,使用带有适当模板标签的JavaScript可能更简洁和简单,同时也从大量的构建时依赖项中删除了一个工具。请注意,JavaScript本身也具有您需要的全部三角函数功能。简而言之,SASS在这里有一个真正增加价值的机会,而这在其他地方不容易实现。
我意识到这可能只会影响一小部分读者,但我也不得不承认,CSS变量虽然很好……但与IE11完全不兼容。谁在乎IE11?可悲的是,我的一些客户(取决于行业)在乎。
这就是Sass的便利之处;我们可以做很多事情,同时仍然与较差的、未维护的、较旧的浏览器兼容。如果我放弃它,我可以看到向后兼容变得更加耗时。
我必须支持IE11,并且正在使用CSS变量。
获取此ponyfill – https://github.com/jhildenbiddle/css-vars-ponyfill
还要确保在
:root
元素中声明CSS变量。关于最后一个示例,最好且更可重用的是在混合宏中使用
@content
来将样式分配给前缀。但是,如果您使用Autoprefixer,则无需通过Sass添加前缀。
这里有一些很棒的内容,但感觉有点像两篇不同的文章。一篇是关于处理与Sass中函数同名的新的CSS函数,另一篇是关于为什么Sass在现代Web开发中仍然有用。
感谢您撰写了如此详细的文章,我确实遇到了min max问题,并且无法解决。
至于您的最后一段,我们有不同的混合宏来处理这个问题
我们这样使用它
似乎除法也不行
https://jsbin.com/zaduhiq/1/edit?html,css,js,output