使用相对颜色

The CSS colors module defines relative color syntax, which allows a CSS <color> value to be defined relative to another color. This is a powerful feature that enables easy creation of complements to existing colors — such as lighter, darker, saturated, semi-transparent, or inverted variants — enabling more effective color palette creation.

This article explains relative color syntax, shows what the different options are, and looks at some illustrative examples.

一般语法

A relative CSS color value has the following general syntax structure

css
color-function(from origin-color channel1 channel2 channel3)
color-function(from origin-color channel1 channel2 channel3 / alpha)

/* color space included in the case of color() functions */
color(from origin-color colorspace channel1 channel2 channel3)
color(from origin-color colorspace channel1 channel2 channel3 / alpha)

Relative colors are created using the same color functions as absolute colors, but with different parameters

  1. Include a basic color function (represented by color-function() above) such as rgb(), hsl(), etc. Which one you pick depends on the color model you want to use for the relative color you are creating (the output color).
  2. Pass in the origin color (represented above by origin-color) your relative color will be based on, preceded by the from keyword. This can be any valid <color> value using any available color model including a color value contained in a CSS custom property, system colors, currentColor, or even another relative color.
  3. In the case of the color() function, include the colorspace of the output color.
  4. Provide an output value for each individual channel. The output color is defined after the origin color — represented above by the channel1, channel2, and channel3 placeholders. The channels defined here depend on the color function you are using for your relative color. For example, if you are using hsl(), you would need to define the values for hue, saturation, and lightness. Each channel value can be a new value, the same as the original value, or a value relative to the channel value of the origin color.
  5. Optionally, an alpha channel value for the output color can be defined, preceded by a slash (/). If the alpha channel value is not explicitly specified, it defaults to the alpha channel value of the origin-color (not 100%, which is the case for absolute color values).

The browser converts the origin color to a syntax compatible with the color function then destructures it into component color channels (plus the alpha channel if the origin color has one). These are made available as appropriately-named values inside the color function — r, g, b, and alpha in the case of the rgb() function, l, a, b, and alpha in the case of the lab() function, h, w, b, and alpha in the case of hwb(), etc. — that can be used to calculate new output channel values.

让我们看看相对颜色语法在实际中的应用。以下 CSS 用于设置两个 <div> 元素的样式,一个使用绝对背景色 - red -,另一个使用 rgb() 函数创建的相对背景色,基于相同的 red 颜色值。

css
#one {
  background-color: red;
}

#two {
  background-color: rgb(from red 200 g b);
}

输出如下:

相对颜色使用 rgb() 函数,该函数以 red 作为原始颜色,将其转换为等效的 rgb() 颜色 (rgb(255 0 0)),然后将新颜色定义为具有 200 值的红色通道,以及与原始颜色值相同的绿色和蓝色通道(它使用浏览器在函数内部提供的 gb 值,它们都等于 0)。

这将产生 rgb(200 0 0) 的输出 - 一个稍微深一点的红色。如果我们指定 255 的红色通道值(或只是 r 值),那么产生的输出颜色将与输入值完全相同。浏览器的最终输出颜色(计算值)是一个等效于 rgb(200 0 0) 的 sRGB color() 值 - color(srgb 0.784314 0 0)

注意:如上所述,在计算相对颜色时,浏览器首先要做的是将提供的原始颜色 (red 在上面的示例中) 转换为与正在使用的颜色函数兼容的值 (在本例中为 rgb())。这样做是为了让浏览器能够根据原始颜色计算输出颜色。虽然计算是相对于使用的颜色函数进行的,但实际的输出颜色值取决于颜色的颜色空间。

  • 较旧的 sRGB 颜色函数无法表达可见光谱的全部范围。(hsl()hwb()rgb()) 的输出颜色被序列化为 color(srgb) 以避免这些限制。这意味着通过 HTMLElement.style 属性或 CSSStyleDeclaration.getPropertyValue() 方法查询输出颜色值将返回输出颜色作为 color(srgb ...) 值。
  • 对于较新的颜色函数 (lab()oklab()lch()oklch()),相对颜色输出值以与使用的颜色函数相同的语法表示。例如,如果正在使用 lab() 颜色函数,输出颜色将是一个 lab() 值。

以下五条代码都产生等效的输出颜色。

css
red
rgb(255 0 0)
rgb(from red r g b)
rgb(from red 255 g b)
rgb(from red 255 0 0)

语法灵活性

在函数中提供的解构原始颜色通道值与开发人员设置的输出颜色通道值之间存在重要区别。

重申一下,当定义相对颜色时,原始颜色的通道值将在函数中提供,以便在定义输出颜色通道值时使用。以下示例使用 rgb() 函数定义相对颜色,并使用原始颜色通道值 (作为 rgb 提供) 作为输出通道值,这意味着输出颜色与原始颜色相同。

css
rgb(from red r g b)

但是,在指定输出值时,您完全不需要使用原始颜色通道值。您需要按正确的顺序提供输出通道值(例如,在 rgb() 的情况下,先红色,然后绿色,然后蓝色),但它们可以是您想要的任何值,只要它们是这些通道的有效值。这使得相对 CSS 颜色具有高度的灵活性。

例如,如果您想要,您可以指定如下所示的绝对值,将 red 转换为 blue

css
rgb(from red 0 0 255)
/* output color is equivalent to rgb(0 0 255), full blue */

注意:如果您正在使用相对颜色语法,但输出与原始颜色相同的颜色或完全不基于原始颜色的颜色,那么您实际上并没有创建相对颜色。您不太可能在实际代码库中这样做,而且可能会直接使用绝对颜色值。但是,我们认为解释您可以使用相对颜色语法来做这件事是有用的,作为学习它的一个起点。

您甚至可以混合或重复提供的值。以下示例以稍微深一点的红色作为输入,并输出浅灰色 - 输出颜色的 rgb 通道都设置为原始颜色的 r 通道值。

css
rgb(from rgb(200 0 0) r r r)
/* output color is equivalent to rgb(200 200 200), light gray */

以下示例使用原始颜色的通道值作为输出颜色的 rgb 通道值,但顺序相反。

css
rgb(from rgb(200 170 0) b g r)
/* output color is equivalent to rgb(0 170 200) */

支持相对颜色的颜色函数

在上面的部分中,我们只看到了通过 rgb() 函数定义的相对颜色。但是,可以使用任何现代 CSS 颜色函数来定义相对颜色 - color()hsl()hwb()lab()lch()oklab()oklch()rgb()。每种情况下的语法结构总体上相同,尽管原始颜色值的名称不同,适合正在使用的函数。

下面您可以找到每个颜色函数的相对颜色语法示例。每种情况都是最简单的,输出颜色通道值与原始颜色通道值完全匹配。

css
/* color() with and without alpha channel */
color(from red a98-rgb r g b)
color(from red a98-rgb r g b / alpha)

color(from red xyz-d50 x y z)
color(from red xyz-d50 x y z / alpha)

/* hsl() with and without alpha channel */
hsl(from red h s l)
hsl(from red h s l / alpha)

/* hwb() with and without alpha channel */
hwb(from red h w b)
hwb(from red h w b / alpha)

/* lab() with and without alpha channel */
lab(from red l a b)
lab(from red l a b / alpha)

/* lch() with and without alpha channel */
lch(from red l c h)
lch(from red l c h / alpha)

/* oklab() with and without alpha channel */
oklab(from red l a b)
oklab(from red l a b / alpha)

/* oklch() with and without alpha channel */
oklch(from red l c h)
oklch(from red l c h / alpha)

/* rgb() with and without alpha channel */
rgb(from red r g b)
rgb(from red r g b / alpha)

值得再次提一下,原始颜色的颜色系统不需要与用于创建输出颜色的颜色系统匹配。同样,这也提供了很大的灵活性。通常,您不会对原始颜色定义的系统感兴趣,甚至可能不知道它是什么(您可能只是有一个 自定义属性值 要进行操作)。您只需要输入一个颜色,例如,通过将其放入 hsl() 函数中并改变亮度值来创建一个更浅的变体。

注意:可以使用诸如 rgba()hsla() 之类的别名来输出相对颜色,并指定原始颜色。当使用传统颜色函数输出相对颜色时,必须使用无逗号的现代语法,并且不能混合百分比和数字。

使用自定义属性

在创建相对颜色时,您可以使用在 CSS 自定义属性 中定义的值,用于原始颜色和输出颜色通道值定义中的值。让我们来看一个例子。

在以下 CSS 中,我们定义了两个自定义属性。

  • --base-color 包含我们的基本品牌颜色 - purple。在这里,我们使用的是命名颜色关键字,但相对颜色可以接受任何颜色语法作为原始颜色。
  • --standard-opacity 包含我们想要应用于半透明框的标准品牌不透明度值 - 0.75

然后,我们给两个 <div> 元素设置背景颜色。一个被赋予绝对颜色 - 我们的 --base-color 品牌紫色。另一个被赋予一个相对颜色,等于我们的品牌紫色,并转化为添加一个等于我们的标准不透明度值的 alpha 通道。

css
:root {
  --base-color: purple;
  --standard-opacity: 0.75;
}

#one {
  background-color: var(--base-color);
}

#two {
  background-color: hwb(from var(--base-color) h w b / var(--standard-opacity));
}

输出如下:

使用数学函数

您可以使用 CSS 数学函数(例如 calc())来计算输出颜色通道的值。让我们来看一个例子。

以下 CSS 用于设置三个 <div> 元素的样式,它们具有不同的背景颜色。中间一个被赋予未修改的 --base-color,而左右两个被赋予该 --base-color 的浅色和深色变体。这些变体使用相对颜色定义 - --base-color 被传递到 lch() 函数中,输出颜色的亮度通道使用 calc() 函数修改以实现所需的效果。浅色变体在亮度通道中添加了 20%,深色变体则从中减去了 20%。

css
:root {
  --base-color: orange;
}

#one {
  background-color: lch(from var(--base-color) calc(l + 20) c h);
}

#two {
  background-color: var(--base-color);
}

#three {
  background-color: lch(from var(--base-color) calc(l - 20) c h);
}

输出如下:

通道值解析为 <number>

为了使通道值计算在相对颜色中有效,所有原始颜色通道值都解析为适当的 <number> 值。例如,在上面的 lch() 示例中,我们通过从原始颜色的 l 通道值中添加或减去数字来计算新的亮度值。如果我们尝试执行 calc(l + 20%),这将导致无效的颜色 - l 是一个 <number>,不能添加 <percentage>

  • 最初指定为 <percentage> 的通道值解析为适合输出颜色函数的 <number>
  • 最初指定为 <hue> 角度的通道值解析为介于 0360(包含)之间的度数。

检查不同的 颜色函数页面,了解其原始通道值解析为的具体内容。

检查浏览器支持

您可以通过在 @supports at-规则中运行它来检查浏览器是否支持相对颜色语法。

例如:

css
@supports (color: hsl(from white h s l)) {
  /* safe to use hsl() relative color syntax */
}

示例

注意:您可以在不同的函数符号类型页面上找到展示使用相对颜色语法的其他示例:color()hsl()hwb()lab()lch()oklab()oklch()rgb().

颜色调色板生成器

此示例允许您选择基本颜色和调色板类型。然后,浏览器将根据选择的基色显示相应的调色板。调色板选择如下:

  • 互补色:包含两种位于色轮对面的颜色,或者换句话说,相反的色调(有关色调和色轮的更多信息,请参阅 <hue> 数据类型)。这两种颜色被定义为基色和基色加上色调通道 +180 度。
  • 三色:包含三种在色轮上等距分布的颜色。这三种颜色被定义为基色,基色加上色调通道 -120 度,以及基色加上色调通道 +120 度。
  • 四色:包含四种在色轮上等距分布的颜色。这四种颜色被定义为基色,以及基色加上色调通道 +90、+180 和 +270 度。
  • 单色:包含多种具有相同色调但亮度值不同的颜色。在我们的示例中,我们在单色调色板中定义了五种颜色 - 基色,以及基色加上亮度通道 -20、-10、+10 和 +20。

HTML

为了参考,下面包含了完整的 HTML 代码。最有趣的部分如下:

  • --base-color 自定义属性存储为 <div> 元素 (ID 为 container) 上的内联 style。我们将其放置在那里,以便使用 JavaScript 很容易更新其值。我们提供了一个初始值 #ff0000 (red),以便在示例加载时显示基于该值的调色板。注意,通常我们可能将其设置在 <html> 元素上,但 MDN 实时示例在渲染时会将其删除。
  • 基础颜色选择器使用 <input type="color"> 控件创建。当在此控件中设置新值时,--base-color 自定义属性将使用 JavaScript 设置为该值,从而生成新的调色板。所有显示的颜色都是基于 --base-color 的相对颜色。
  • 一组 <input type="radio"> 控件允许选择要生成的调色板类型。当在此处选择新值时,JavaScript 用于在 container <div> 上设置一个新类以表示所选调色板。在 CSS 中,后代选择器用于定位子 <div>(例如 .comp :nth-child(1)),以便它们可以应用正确的颜色并隐藏未使用的 <div> 节点。
  • container <div> 包含显示生成的调色板颜色的子 <div>。请注意,在其上设置了初始类 comp,以便页面在首次加载时显示互补配色方案。
html
<div>
  <h1>Color palette generator</h1>
  <form>
    <div id="color-picker">
      <label for="color">Select a base color:</label>
      <input type="color" id="color" name="color" value="#ff0000" />
    </div>
    <div>
      <fieldset>
        <legend>Select a color palette type:</legend>

        <div>
          <input
            type="radio"
            id="comp"
            name="palette-type"
            value="comp"
            checked />
          <label for="comp">Complementary</label>
        </div>

        <div>
          <input
            type="radio"
            id="triadic"
            name="palette-type"
            value="triadic" />
          <label for="triadic">Triadic</label>
        </div>

        <div>
          <input
            type="radio"
            id="tetradic"
            name="palette-type"
            value="tetradic" />
          <label for="tetradic">Tetradic</label>
        </div>

        <div>
          <input
            type="radio"
            id="monochrome"
            name="palette-type"
            value="monochrome" />
          <label for="monochrome">Monochrome</label>
        </div>
      </fieldset>
    </div>
  </form>
  <div id="container" class="comp" style="--base-color: #ff0000;">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
  </div>
</div>

CSS

下面我们只显示设置调色板颜色的 CSS。请注意,在每种情况下,如何使用后代选择器将正确的 background-color 应用于每个子 <div> 以获得所选调色板。我们更关心 <div> 在源顺序中的位置,而不是元素类型,因此我们使用了 :nth-child 来定位它们。

在最后一个规则中,我们使用了 通用兄弟选择器 (~) 来定位每个调色板类型中未使用的 <div> 元素,并设置 display: none 以阻止它们呈现。

颜色本身包括 --base-color,以及从该 --base-color 派生的相对颜色。相对颜色使用 lch() 函数 - 传入源 --base-color 并根据需要定义具有调整亮度或色调通道的输出颜色。

css
/* Complementary colors */
/* Base color, and base color with hue channel +180 degrees */

.comp :nth-child(1) {
  background-color: var(--base-color);
}

.comp :nth-child(2) {
  background-color: lch(from var(--base-color) l c calc(h + 180));
}

/* Use @supports to add in support old syntax that requires deg units
   to be specified in hue calculations. This is required for Safari 16.4+. */
@supports (color: lch(from red l c calc(h + 180deg))) {
  .comp :nth-child(2) {
    background-color: lch(from var(--base-color) l c calc(h + 180deg));
  }
}

/* Triadic colors */
/* Base color, base color with hue channel -120 degrees, and base color */
/* with hue channel +120 degrees */

.triadic :nth-child(1) {
  background-color: var(--base-color);
}

.triadic :nth-child(2) {
  background-color: lch(from var(--base-color) l c calc(h - 120));
}

.triadic :nth-child(3) {
  background-color: lch(from var(--base-color) l c calc(h + 120));
}

/* Use @supports to add in support old syntax that requires deg units
   to be specified in hue calculations. This is required for Safari 16.4+. */
@supports (color: lch(from red l c calc(h + 120deg))) {
  .triadic :nth-child(2) {
    background-color: lch(from var(--base-color) l c calc(h - 120deg));
  }

  .triadic :nth-child(3) {
    background-color: lch(from var(--base-color) l c calc(h + 120deg));
  }
}

/* Tetradic colors */
/* Base color, and base color with hue channel +90, +180, and +270 degrees */

.tetradic :nth-child(1) {
  background-color: var(--base-color);
}

.tetradic :nth-child(2) {
  background-color: lch(from var(--base-color) l c calc(h + 90));
}

.tetradic :nth-child(3) {
  background-color: lch(from var(--base-color) l c calc(h + 180));
}

.tetradic :nth-child(4) {
  background-color: lch(from var(--base-color) l c calc(h + 270));
}

/* Use @supports to add in support old syntax that requires deg units
   to be specified in hue calculations. This is required for Safari 16.4+. */
@supports (color: lch(from red l c calc(h + 90deg))) {
  .tetradic :nth-child(2) {
    background-color: lch(from var(--base-color) l c calc(h + 90deg));
  }

  .tetradic :nth-child(3) {
    background-color: lch(from var(--base-color) l c calc(h + 180deg));
  }

  .tetradic :nth-child(4) {
    background-color: lch(from var(--base-color) l c calc(h + 270deg));
  }
}

/* Monochrome colors */
/* Base color, and base color with lightness channel -20, -10, +10, and +20 */

.monochrome :nth-child(1) {
  background-color: lch(from var(--base-color) calc(l - 20) c h);
}

.monochrome :nth-child(2) {
  background-color: lch(from var(--base-color) calc(l - 10) c h);
}

.monochrome :nth-child(3) {
  background-color: var(--base-color);
}

.monochrome :nth-child(4) {
  background-color: lch(from var(--base-color) calc(l + 10) c h);
}

.monochrome :nth-child(5) {
  background-color: lch(from var(--base-color) calc(l + 20) c h);
}

/* Hide unused swatches for each palette type */
.comp :nth-child(2) ~ div,
.triadic :nth-child(3) ~ div,
.tetradic :nth-child(4) ~ div {
  display: none;
}
关于 @supports 测试的说明

在示例 CSS 中,您会注意到使用了 @supports 块来为支持先前版本相对颜色语法规范草案的浏览器提供不同的 background-color 值。这是必需的,因为 Safari 的初始实现基于规范的较旧版本,其中源颜色通道值解析为 <number> 或其他单位类型,具体取决于上下文。这意味着在执行加法和减法时,值有时需要单位,这会造成混乱。在较新的实现中,源颜色通道值始终解析为等效的 <number> 值,这意味着计算始终使用无单位值进行。

请注意,每种情况下的支持测试是如何使用简单声明完成的 - 例如 color: lch(from red l c calc(h + 90deg)) - 而不是我们需要为其他浏览器更改的实际值。在测试诸如此类的复杂值时,您应该使用最简单的声明,该声明仍然包含您要测试的语法差异。

@supports 测试中包含自定义属性不起作用 - 无论给自定义属性分配什么值,测试始终返回为正。这是因为自定义属性值仅在被分配为常规 CSS 属性的无效值(或无效值的一部分)时才会变为无效。为了解决此问题,我们在每个测试中将 var(--base-color) 替换为 red 关键字。

JavaScript

在 JavaScript 中,我们

  • 向单选按钮添加 change 事件监听器,以便当选择一个按钮时,setContainer() 函数运行。此函数使用所选单选按钮的值更新具有 id="container"<div>class 值,以便为所选调色板类型将正确的背景颜色应用于子 <div>
  • 向颜色选择器控件添加 input 事件监听器,以便当选择新颜色时,setBaseColor() 函数运行。此函数将 --base-color 自定义属性的值设置为新颜色。
js
const form = document.forms[0];
const radios = form.elements["palette-type"];
const colorPicker = form.elements["color"];
const containerElem = document.getElementById("container");

for (const radio of radios) {
  radio.addEventListener("change", setContainer);
}

colorPicker.addEventListener("input", setBaseColor);

function setContainer(e) {
  const palType = e.target.value;
  console.log("radio changed");
  containerElem.setAttribute("class", palType);
}

function setBaseColor(e) {
  console.log("color changed");
  containerElem.style.setProperty("--base-color", e.target.value);
}

结果

输出如下。这开始显示相对 CSS 颜色的强大功能 - 我们定义了多种颜色并生成了通过调整单个自定义属性而实时更新的调色板。

实时 UI 配色方案更新器

此示例显示一个包含标题和文本的卡片,但有一个变化 - 在卡片下方是一个滑块 (<input type="range">) 控件。当其值更改时,JavaScript 用于将 --hue 自定义属性值设置为新的滑块值。

这反过来会调整整个 UI 的配色方案

  • --base-color 值是一个相对颜色,其色调通道设置为 --hue 的值。
  • 设计中使用的其他颜色是基于 --base-color 的相对颜色。因此,当 --base-color 更改时,它们也会更改。

HTML

下面显示了示例的 HTML。

  • <main> 元素充当外部包装器,用于包含其余内容,从而使卡片和表单能够作为单个单元在 <main> 内垂直和水平居中。
  • <section> 元素包含定义卡片内容的 <h1><p> 元素。
  • <form> 元素包含 (<input type="range">) 控件及其 <label>
html
<main>
  <section>
    <h1>A love of colors</h1>
    <p>
      Colors, the vibrant essence of our surroundings, are truly awe-inspiring.
      From the fiery warmth of reds to the calming coolness of blues, they bring
      unparalleled richness to our world. Colors stir emotions, ignite
      creativity, and shape perceptions, acting as a universal language of
      expression. In their brilliance, colors create a visually enchanting
      tapestry that invites admiration and sparks joy.
    </p>
  </section>
  <form>
    <label for="hue-adjust">Adjust the hue:</label>
    <input
      type="range"
      name="hue-adjust"
      id="hue-adjust"
      value="240"
      min="0"
      max="360" />
  </form>
</main>

CSS

在 CSS 中,:root 上设置了默认的 --hue 值,相对 lch() 颜色以定义配色方案,以及填充整个主体的径向渐变。

相对颜色如下

  • --base-color:基本颜色采用 red 的源颜色(尽管任何全色都可以),并将其色调值调整为 --hue 自定义属性中设置的值。
  • --bg-color--base-color 的一个更浅的变体,旨在用作背景。这是通过采用 --base-color 的源颜色并在其亮度值上加 40 来创建的。
  • --complementary-color:与 --base-color 在色轮上相距 180 度的互补色。这是通过采用 --base-color 的源颜色并在其色调值上加 180 来创建的。

现在看一下其余的 CSS,并注意所有使用这些颜色的地方。这包括 背景边框text-shadow,甚至滑块的 accent-color

注意: 为简洁起见,仅显示与相对颜色使用相关的 CSS 部分。

css
:root {
  /* Default hue value */
  --hue: 240;

  /* Relative color definitions */
  --base-color: lch(from red l c var(--hue));
  --bg-color: lch(from var(--base-color) calc(l + 40) c h);
  --complementary-color: lch(from var(--base-color) l c calc(h + 180));

  background: radial-gradient(ellipse at center, white 20%, var(--base-color));
}

/* Use @supports to add in support for --complementary-color with old
   syntax that requires deg units to be specified in hue calculations.
   This is required for in Safari 16.4+. */
@supports (color: lch(from red l c calc(h + 180deg))) {
  body {
    --complementary-color: lch(from var(--base-color) l c calc(h + 180deg));
  }
}

/* Box styling */

section {
  background-color: var(--bg-color);
  border: 3px solid var(--base-color);
  border-radius: 20px;
  box-shadow: 10px 10px 30px rgb(0 0 0 / 0.5);
}

h1 {
  background-color: var(--base-color);
  text-shadow:
    1px 1px 1px var(--complementary-color),
    -1px -1px 1px var(--complementary-color),
    0 0 3px var(--complementary-color);
}

/* Range slider styling */

form {
  background-color: var(--bg-color);
  border: 3px solid var(--base-color);
}

input {
  accent-color: var(--complementary-color);
}

JavaScript

JavaScript 向滑块控件添加 input 事件监听器,以便当设置新值时,setHue() 函数运行。此函数在 :root<html> 元素)上设置新的内联 --hue 自定义属性值,该值会覆盖我们在 CSS 中设置的原始默认值。

js
const rootElem = document.querySelector(":root");
const slider = document.getElementById("hue-adjust");

slider.addEventListener("input", setHue);

function setHue(e) {
  rootElem.style.setProperty("--hue", e.target.value);
}

结果

输出显示如下。相对 CSS 颜色在这里用于控制整个 UI 的配色方案,该配色方案可以随着单个值的修改而实时调整。

另请参阅