大多数时候,我都在编写原生 CSS。 由于 CSS 变量和嵌套,我很少需要使用 Sass 或其他预处理器。 我使用 Sass 的时候通常是需要一个 @mixin
来循环遍历项目列表或帮助保持通用样式的 DRY(Don't Repeat Yourself,不要重复自己)原则。
在不久的将来,这种情况可能会改变,因为一个新的 CSS 函数和混合模块草案 在 6 月底发布,此前 CSSWG 在 2 月份决定采用该提案。
注意模块的名称:函数 _和_ 混合。 两者之间存在区别。
目前,所有这些都是全新的,而且非常不成熟,草案中有很多 TODO
注释,以及未来草案中需要考虑的要点。 草案规范甚至还没有对混合进行定义。 我们可能还需要一段时间才能获得真正可供使用和实验的东西,但我喜欢在这些事情还处于早期阶段的时候就开始思考它们,因为我知道事情一定会发生变化。
除了早期的草案规范外,Miriam Suzanne 还发布了一篇 详细的解释文章,帮助填补了一些信息空白。 Miriam 是该规范的编辑,所以我发现她关于此的任何文章都是有用的背景信息。
有很多内容要阅读! 以下是我的一些主要收获...
自定义函数是高级自定义属性
我们不是在讨论近年来我们所喜爱的单一用途内置函数,例如 calc()
、min()
、max()
等。 相反,我们指的是使用 @function
at-rule 定义的 **自定义函数**,该函数包含用于返回预期值的逻辑。
这使得自定义函数非常类似于自定义属性。 自定义属性只是对某个预期值的占位符,通常我们会在前面定义它。
:root {
--primary-color: hsl(25 100% 50%);
}
自定义函数看起来很相似,只是它们使用 @function
定义并接受参数。 这是目前草案规范中的语法。
@function <function-name> [( <parameter-list> )]? {
<function-rules>
result: <result>;
}
result
是自定义函数最终计算出的值。 目前对我来说有点令人困惑,但我对此的理解是,自定义 _函数_ 返回一个自定义 _属性_。 以下是一个 来自规范草案的示例(略作修改),它计算圆形的面积。
@function --circle-area(--r) {
--r2: var(--r) * var(--r);
result: calc(pi * var(--r2));
}
调用函数有点类似于声明自定义属性,只是没有使用 var()
,并且为定义的参数提供了参数。
.element {
inline-size: --circle-area(--r, 1.5rem); /* = ~7.065rem */
}
看起来我们可以使用当前的 CSS 功能来实现与自定义属性相同的效果。
:root {
--r: 1rem;
--r2: var(--r) * var(--r);
--circle-area: calc(pi * var(--r2));
}
.element {
inline-size: var(--circle-area, 1.5rem);
}
也就是说,我们选择自定义函数而不是自定义属性的原因是:(1)它们可以在一次操作中返回多个值中的一个,以及(2)它们支持条件规则,例如 @supports
和 @media
来确定返回哪个值。 请查看 Miriam 关于自定义函数的示例,该函数根据视窗的内联尺寸返回多个值中的一个。
/* Function name */
@function --sizes(
/* Array of possible values */
--s type(length),
--m type(length),
--l type(length),
/* The returned value with a default */
) returns type(length) {
--min: 16px;
/* Conditional rules */
@media (inline-size < 20em) {
result: max(var(--min), var(--s, 1em));
}
@media (20em < inline-size < 50em) {
result: max(var(--min), var(--m, 1em + 0.5vw));
}
@media (50em < inline-size) {
result: max(var(--min), var(--l, 1.2em + 1vw));
}
}
Miriam 接着 解释 了为什么像这样以逗号分隔的参数列表需要额外的 CSSWG 工作,因为它可能会被误认为是复合选择器。
混合有助于保持 DRY、可重用的样式块
与自定义函数相比,混合对我来说更熟悉。 多年编写 Sass 混合会让你产生这种感觉,而且事实上,这也是我偶尔仍然会使用 Sass 的主要原因。
混合有点像新的自定义函数。 我们使用 @mixin
而不是 @function
,这 与 Sass 中的工作方式完全相同。
/* Custom function */
@function <function-name> [( <parameter-list> )]? {
<function-rules>
result: <result>;
}
/* CSS/Sass mixin */
@mixin <mixin-name> [( <parameter-list> )]? {
<mixin-rules>
}
因此,自定义函数和混合非常相似,但它们确实有所不同。
- 函数使用
@function
定义; 混合使用@mixin
定义,但都用带连字符的标识符命名(例如--name
)。 - 函数 _返回_ 一个值; 混合 _产生_ 样式规则。
这使得混合非常适合抽象可能用作实用程序类的样式,例如用于隐藏文本的类,该文本可以被屏幕阅读器读取。
.sr-text {
position: absolute;
left: -10000px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
真正实用的是,我们可以在 HTML 中的元素上添加这个类来隐藏文本。
<a class="sr-text">Skip to main content</a>
非常方便! 但是,正如任何讨厌 Tailwind 的人会告诉你,如果我们依赖 _许多_ 实用程序类,这会导致难以理解的丑陋标记。 屏幕阅读器文本不太可能出现这种情况,但以下来自 Tailwind 文档 的一个快速示例应该说明这一点。
<div class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
这真的只是个人喜好问题。 但是回到混合! 关键是我们可以将实用程序类几乎用作 CSS 片段来构建其他样式规则,并保持标记和样式之间的更清晰分离。 如果我们从之前相同的 .sr-text
样式开始,并将它们混合(是的,我正在创造这个词)
@mixin --sr-text {
position: absolute;
left: -10000px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
我们可以使用新的 @apply
at-rule 将这些样式嵌入到其他 CSS 样式规则中,而不是跳转到 HTML 中应用这些样式。
header a:first-child {
@apply --sr-text;
/* Results in: */
position: absolute;
left: -10000px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
可能更好的示例是每个项目似乎都需要的东西:**居中!**
@mixin --center-me {
display: grid;
place-items: center;
}
现在它可以成为一个更大的规则集的一部分。
header {
@apply --center-me;
/*
display: grid;
place-items: center;
*/
background-color: --c-blue-50;
color: --c-white;
/* etc. */
}
这与 Sass 不同,Sass 使用 @include
来调用混合,而不是 @apply
。 我们甚至可以返回更大的样式块,例如元素 ::before
和 ::after
伪类的样式。
@mixin --center-me {
display: grid;
place-items: center;
position: relative;
&::after {
background-color: hsl(25 100% 50% / .25);
content: "";
height: 100%;
position: absolute;
width: 100%;
}
}
当然,我们已经看到混合像自定义函数一样接受参数。 如果您希望放松样式以进行变体,例如使用不同的颜色定义一致的渐变,则可以使用参数。
@mixin --gradient-linear(--color-1, --color-2, --angle) {
/* etc. */
}
我们可以为每个参数指定语法,作为一种类型检查形式。
@mixin --gradient-linear(
--color-1 type(color),
--color-2 type(color),
--angle type(angle),
) {
/* etc. */
}
我们可以进一步抽象这些变量,并在它们上面设置默认值。
@mixin --gradient-linear(
--color-1 type(color),
--color-2 type(color),
--angle type(angle),
) {
--from: var(--color-1, orangered);
--to: var(--from-color, goldenrod);
--angle: var(--at-angle, to bottom right);
/* etc. */
}
…然后我们使用参数作为变量占位符来编写混合的样式规则。
@mixin --gradient-linear(
--color-1 type(color),
--color-2 type(color),
--angle type(angle),
) {
--from: var(--color-1, orangered);
--to: var(--from-color, goldenrod);
--angle: var(--at-angle, to bottom right);
background: linear-gradient(var(--angle), var(--from), var(--to));
}
如果您愿意,可以在其中添加条件逻辑。
@mixin --gradient-linear(
--color-1 type(color),
--color-2 type(color),
--angle type(angle),
) {
--from: var(--color-1, orangered);
--to: var(--from-color, goldenrod);
--angle: var(--at-angle, to bottom right);
background: linear-gradient(var(--angle), var(--from), var(--to));
@media (prefers-contrast: more) {
background: color-mix(var(--from), black);
color: white;
}
}
所有这些都可以在我们想要的任何规则集中 @apply
该混合。
header {
@apply --gradient-linear;
/* etc. */
}
.some-class {
@apply --gradient-linear;
/* etc. */
}
…并将它们与其他混合结合起来。
header {
@apply --gradient-linear;
@apply --center-me;
/* etc. */
}
所有这些都是非常高级别的。 Miriam 详细介绍了一些细微差别,例如:
- 在根级别(即不在选择器中)应用混合。
- 使用容器查询,但必须在另一个元素上设置全局自定义属性,而不是查询的元素。
- 使用类似于混合中的
@when
/@else
来有条件地设置混合参数的可能性。(这让我很好奇 新提出的if()
函数 以及它是否将用于替代@when
)。 - 为什么我们可能在支持循环的方式上与 Sass 画一条线。(CSS 是一种声明性语言,而循环是命令式流程)。
- 混合的作用域(
@layer
?scope
?其他东西?)。
Miriam 有一个 关于混合周围的开放问题和讨论的出色概述。
就是这样,至少现在是这样。
天哪,这对于我的金发脑袋来说太多了! 每当我深入 CSS 规范草案时,我都要提醒自己,尘埃还没有落定。 规范作者和编辑正在与我们一样,甚至更多地努力解决着同样的问题,因此,仅仅阅读一下草案并不会让任何人成为专家。 而且这还不包括在所有这些成为浏览器实现的推荐功能之前,事情可能会发生变化,而且很可能也会发生变化。
这将是一个有趣的领域,您可以使用以下资源来关注它。
- 提案:自定义 CSS 函数和混合(GitHub 问题 #9350)
- CSS 混合 _及_ 函数解释(Miriam Suzanne)
- 分层切换:可选 CSS 混合(Roman Komarov)
- 所有标记为
css-mixins
的 GitHub 问题。