CSS 函数和混合模块说明

Avatar of Geoff Graham
Geoff Graham

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费信用额度!

大多数时候,我都在编写原生 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 是一种声明性语言,而循环是命令式流程)。
  • 混合的作用域(@layerscope?其他东西?)。

Miriam 有一个 关于混合周围的开放问题和讨论的出色概述

就是这样,至少现在是这样。

天哪,这对于我的金发脑袋来说太多了! 每当我深入 CSS 规范草案时,我都要提醒自己,尘埃还没有落定。 规范作者和编辑正在与我们一样,甚至更多地努力解决着同样的问题,因此,仅仅阅读一下草案并不会让任何人成为专家。 而且这还不包括在所有这些成为浏览器实现的推荐功能之前,事情可能会发生变化,而且很可能也会发生变化。

这将是一个有趣的领域,您可以使用以下资源来关注它。