使用 CSS 自定义函数

CSS 自定义函数使你能够创建可重用的 CSS 代码块,这些代码块可以接受参数,包含复杂的逻辑(使用 CSS if() 函数和 @media @规则等特性定义),并根据该逻辑返回值。它们的工作方式类似于 CSS 自定义属性,但提供了更大的灵活性。

在本文中,我们将向你展示如何使用它们,并提供一些实际的例子。

函数基础

一个基本的 CSS 自定义函数定义如下所示:

css
@function --half-opacity() {
  result: 0.5;
}

@function 语法之后,我们为函数定义一个名称:--half-opacity。这必须是一个 <dashed-ident> 类型——它必须以双破折号开头,并且区分大小写。函数名后紧跟一对圆括号(())和一对花括号({})。

注意:如果多个 CSS 函数被赋予相同的名称,则在级联中更强的 @layer 中的函数会获胜。如果它们都在同一层中,则源代码中最后定义的函数会获胜。

花括号内是函数的主体,这里定义了函数的逻辑。这可以包含多个声明,包括自定义属性(其作用域将限于函数主体)、诸如 @media 之类的 @规则,以及 result 描述符。result 描述符的值被计算以确定函数返回的值。

在这里,我们将 result 设置为值 0.5--half-opacity() 函数将始终返回 0.5

为什么是“result”而不是“return”?

result 描述符在功能上听起来类似于 JavaScript 函数的 return 语句。然而,CSS 函数中不使用 return。这是因为,与 JavaScript 的 return 语句不同,CSS 函数不会在遇到 result 声明时立即返回值。

CSS 函数的主体从头到尾进行计算。如果主体中包含多个 result 声明,则源代码中最后一个会覆盖前面的。

调用 CSS 函数

可以使用 <dashed-function> 语法在任何合适的属性值位置调用 CSS 函数,该语法由函数名后跟一对圆括号组成,圆括号内包含要传递给函数的参数(如果有)。例如,我们可以像这样调用我们的 --half-opacity() 函数:

css
h2 {
  opacity: --half-opacity();
}

由于此函数始终返回值 0.5,因此前面的声明等同于 opacity: 0.5。这并不是很有用。你还不如直接使用自定义属性或字面量值 0.5

让我们继续看看如何使用 CSS 函数。

CSS 函数的特性检测

CSS 函数在没有参数时的一个实际用途是特性检测。在本文我们将要看到的所有示例中,我们定义了一个 --supports() 函数,它看起来像这样:

css
@function --supports() {
  result: none;
}

然后,你可以定义一个“功能不支持”的横幅,并将其 display 属性设置为 --supports()

html
<p class="support">
  ⚠️ Your browser doesn't currently support CSS custom functions.
</p>
css
.support {
  /* ... */
  display: --supports();
}

在支持自定义函数的浏览器中,display 将被设置为 none,支持横幅将被隐藏。在不支持的浏览器中,display: --supports() 声明将是无效的,因此会被忽略;因此,横幅将被显示。

指定函数参数

CSS 函数参数在函数名后的圆括号内指定为逗号分隔的自定义属性。例如:

css
@function --transparent(--color, --alpha) {
  result: oklch(from var(--color) l c h / var(--alpha));
}

该函数名为 --transparent,并接受两个自定义属性作为参数,--color--alpha,它们可以在函数主体内部局部使用。主体包含一个 result 描述符,它使用 CSS 相对颜色语法将输入的 --color 值转换为一个 oklch() 颜色,其 alpha 通道值由输入的 --alpha 值指定。

然后,你可以在任何想要生成现有颜色半透明版本的地方调用此函数。

例如

css
section {
  --base-color: #faa6ff;
  background-color: --transparent(var(--base-color), 0.8);
}

指定数据类型

可以为函数参数和返回值指定允许的数据类型。当你不指定这些时,函数将接受任何类型的值。

让我们修改我们之前的函数以提供数据类型:

css
@function --transparent(--color type(<color>), --alpha type(<number>)) returns
  type(<color>) {
  result: oklch(from var(--color) l c h / var(--alpha));
}

每个参数的数据类型在参数名后指定,而 result 的数据类型在左花括号前指定,并以 returns 关键字开头。type() 函数用于指定数据类型。

请注意,在只指定单个数据类型的情况下,可以省略 type() 语法,直接将类型写成简写形式:

css
@function --transparent(--color <color>, --alpha <number>) returns <color> {
  result: oklch(from var(--color) l c h / var(--alpha));
}

现在,只有当输入参数分别为 <color><number>,并且 result 是一个 <color> 时,该函数才会产生有效值。否则,例如:

css
section {
  --base-color: #faa6ff;
  background-color: --transparent(var(--base-color), 50%);
}

那么该值将在计算值时变得无效(因为 50% 不是 <number> 而是 <percentage>),background-color 最终将被设置为 transparent

指定多个允许的类型

你可以使用 | 符号作为分隔符来指定多个可接受的数据类型,例如:

css
@function --transparent(--color <color>, --alpha type(<number> | <percentage>))
  returns <color> {
  result: oklch(from var(--color) l c h / var(--alpha));
}

在这种情况下,必须使用完整的 type() 语法。

经过这个调整,--transparent(var(--base-color), 50%) 函数调用现在是有效的。

指定默认值

你还可以在参数定义的末尾,用冒号指定参数的默认值。例如:

css
@function --transparent(--color <color>, --alpha <number>: 0.8) returns <color> {
  result: oklch(from var(--color) l c h / var(--alpha));
}

--alpha 参数的默认值现在是 0.8。如果你想使用这个值,可以在调用函数时省略第二个参数:

css
section {
  --base-color: #faa6ff;
  background-color: --transparent(var(--base-color));
}

注意:如果一个无效的值作为函数参数传入,并且该参数定义中指定了默认值,那么无效值将被忽略,而使用默认值。

颜色调整函数示例

你可以在我们的 color-adjust-functions 示例中看到 --transparent() 函数的实际效果(参见源代码)。

这个例子还包括名为 --lighter()--darker() 的函数,它们的工作方式与 --transparent() 类似,但分别返回颜色的更亮和更暗的变体:

css
@function --transparent(--color <color>, --alpha <number>: 0.8) returns <color> {
  result: oklch(from var(--color) l c h / var(--alpha));
}

@function --lighter(--color <color>, --lightness-adjust <number>: 0.2) returns
  <color> {
  result: oklch(from var(--color) calc(l + var(--lightness-adjust)) c h);
}

@function --darker(--color <color>, --lightness-adjust <number>: 0.2) returns
  <color> {
  result: oklch(from var(--color) calc(l - var(--lightness-adjust)) c h);
}

像这样的函数库对于基于单一颜色定义颜色方案非常有用:

css
:root {
  --base-color: #faa6ff;
}

section {
  background-color: --transparent(var(--base-color));
  border: 3px solid --lighter(var(--base-color), 0.1);
  color: --darker(var(--base-color), 0.55);
}

包含复杂逻辑

你可以使用诸如 @media @规则和 if() 函数之类的构造在函数中包含更复杂的逻辑。

我们的 responsive-narrow-wide 示例(参见源代码)展示了一个名为 --narrow-wide() 的函数,它可以为任何属性提供两个值选项。一个在视口低于特定断点时设置,另一个在视口高于该断点时设置。

--narrow-wide() 函数接受两个参数,--narrow--wide。返回的 result--wide 属性,除非视口宽度小于 700px,在这种情况下返回 --narrow

css
@function --narrow-wide(--narrow, --wide) {
  result: var(--wide);
  @media (width < 700px) {
    result: var(--narrow);
  }
}

该函数可用于在多种上下文中提供响应式的值选项:

css
body {
  display: grid;
  grid-template-columns: repeat(--narrow-wide(1, 3), 1fr);
  gap: --narrow-wide(0, 20px);
  padding: 0 20px;
}

h2 {
  font-size: --narrow-wide(2.5rem, 2rem);
}

p {
  font-size: --narrow-wide(1.4rem, 1rem);
  line-height: 1.5;
}

使用 if() 函数

我们可以使用 if() 函数来重写 --narrow-wide() 函数:

css
@function --narrow-wide(--narrow, --wide) {
  result: if(media(width < 700px): var(--narrow) ; else: var(--wide));
}

一次编写复杂语法,然后重用

CSS 函数的一个关键用例是定义一次复杂的语法片段,然后能够通过更简单的函数调用多次重用它。

我们的 gradient-function 示例(参见源代码)提供了这方面的一个例子。它有一个名为 --shippo-pattern() 的函数,该函数接受长度和颜色参数,并返回一个复杂的 background 值,该值包含多个 radial-gradient() 背景:

css
@function --shippo-pattern(--size <length>, --tint <color>) {
  result:
    radial-gradient(closest-side, transparent 98%, rgb(0 0 0 / 0.3) 99%) 0 0 /
      var(--size) var(--size),
    radial-gradient(closest-side, transparent 98%, rgb(0 0 0 / 0.3) 99%)
      calc(var(--size) / 2) calc(var(--size) / 2) / var(--size) var(--size)
      var(--tint);
}

定义了这个函数后,我们现在可以创建具有不同色调和圆圈大小的该背景值的变体:

css
#one {
  background: --shippo-pattern(100px, #def);
}

#two {
  background: --shippo-pattern(3.5rem, lime);
}

#three {
  background: --shippo-pattern(10vw, purple);
}

另见