在專案中,顏色有時可能會失控。我們通常從幾個精心挑選的品牌色開始,但隨著專案的擴充套件,我們可能會發現自己添加了各種變體。也許我們意識到出於可訪問性原因需要調整按鈕顏色的亮度,或者需要元件的細微不同的變體。我們如何確保所選顏色符合我們專案的“設計系統”?
我一直在探索使用相對較新的 CSS color-mix() 函式來實現這一目標。看到我可以生成不同的調色盤變體,這很有趣!讓我們深入瞭解 color-mix() 如何改變你的設計過程。
color-mix() 函式允許我們指定要混合的兩種顏色,然後輸出結果。我們可以控制混合中每種顏色的比例,以及顏色插值空間,後者決定了顏色如何混合在一起。

顏色插值方法是一個必需的引數。我們將在後面的部分介紹它。現在,我們將使用 srgb 來舉例說明。
我們將每種顏色的比例指定為百分比。如果我們省略兩種顏色的百分比,color-mix() 預設將使用每種 50%。如下所示,將 red 和 blue 以相等的比例混合,會得到預期的 purple 色調。
<div class="row">
<div class="mix"></div>
<div class="result"></div>
</div>
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
display: grid;
place-items: center;
}
.row {
display: flex;
gap: 1rem;
min-width: min(100%, 40rem);
}
.mix,
.result {
border: 2px solid;
}
.mix {
flex: 1 0 auto;
background: linear-gradient(to right, red 50%, blue 0);
}
.result {
aspect-ratio: 4 / 3;
flex: 0 0 20%;
}
.result {
background-color: color-mix(in srgb, blue, red);
}
如果我們只為一種顏色指定百分比,則另一種顏色的百分比會自動調整,使總和達到 100%。例如,無論我們將 blue 指定為 90% 還是將 red 指定為 10%,結果都是相同的——一種大部分為藍色、略帶紅色的顏色。
<div class="wrapper">
<div class="group">
<code>color-mix(in srgb, blue 90%, red)</code>
<div class="row">
<div class="mix"></div>
<div class="result"></div>
</div>
</div>
<div class="group">
<code>color-mix(in srgb, blue, red 10%)</code>
<div class="row">
<div class="mix"></div>
<div class="result"></div>
</div>
</div>
</div>
* {
box-sizing: border-box;
}
body {
padding: 1rem;
}
code {
display: block;
margin-bottom: 0.75rem;
font-size: 1.1rem;
font-weight: 600;
}
.wrapper {
width: min(100%, 40rem);
margin: 2rem auto;
display: grid;
gap: 2rem;
}
.row {
display: flex;
gap: 1rem;
width: 100%;
}
.mix {
flex: 1 0 auto;
background: linear-gradient(to right, blue 90%, red 0);
border: 2px solid;
}
.result {
flex: 0 0 20%;
background: color-mix(in srgb, blue 90%, red);
border: 2px solid;
aspect-ratio: 4 / 3;
}
.group:nth-child(2) .result {
background: color-mix(in srgb, blue, red 10%);
}
/* Both these will produce the same resultant color */
color-mix(in srgb, blue 90%, red)
color-mix(in srgb, blue, red 10%)
如果兩種顏色的百分比之和小於 100%,color-mix() 的行為略有不同:總和將作為 alpha 乘數儲存,並且這兩種顏色將使用此乘數進行縮放,直到它們的總和達到 100%。(有關幾個示例,請參閱規範中的 百分比規範化 部分)。
儘管下面的兩個 color-mix() 函式混合了相同比例的顏色,但第二個函式(百分比之和為 40%)產生的顏色相同,但 alpha 值為 0.4。
<div class="wrapper">
<div class="group">
<code>color-mix(in srgb, blue, red)</code>
<div class="row">
<div class="mix"></div>
<div class="result"></div>
</div>
</div>
<div class="group">
<code>color-mix(in srgb, blue 20%, red 20%)</code>
<div class="row">
<div class="mix"></div>
<div class="result"></div>
</div>
</div>
</div>
* {
box-sizing: border-box;
}
body {
padding: 1rem;
}
code {
display: block;
margin-bottom: 0.75rem;
font-size: 1.1rem;
font-weight: 600;
}
.wrapper {
width: min(100%, 40rem);
margin: 2rem auto;
display: grid;
gap: 2rem;
}
.group .row {
display: flex;
gap: 1rem;
width: 100%;
}
.mix {
flex: 1 0 auto;
background: linear-gradient(to right, blue 50%, red 0);
border: 2px solid;
}
.result {
flex: 0 0 20%;
background: color-mix(in srgb, blue, red);
border: 2px solid;
aspect-ratio: 4 / 3;
}
.group:nth-child(2) .mix {
background: linear-gradient(
to right,
blue 20%,
red 0,
red 40%,
transparent 0
);
}
.group:nth-child(2) .result {
background: color-mix(in srgb, blue 20%, red 20%);
}
/* Result: rgb(128 0 128) */
color-mix(in srgb, blue, red)
/* Result: rgb(128 0 128 / 0.4) */
color-mix(in srgb, blue 20%, red 20%)
作為一個典型的用例,我們經常需要建立品牌顏色的淺色或深色變體。為了實現這一點,我們可以使用 color-mix() 以不同的比例將白色或黑色混合到我們的基礎顏色中。
下面的示例演示瞭如何將不同比例的 white 和 black 與基礎顏色 blue 混合,以建立其淺色和深色變體,展示了 color-mix() 在調整基礎顏色強度方面的應用。
<div class="row">
<div class="square bg-blue-lighter"></div>
<div class="square bg-blue-light"></div>
<div class="square bg-blue"></div>
<div class="square bg-blue-dark"></div>
<div class="square bg-blue-darker"></div>
</div>
* {
box-sizing: border-box;
}
.row {
display: flex;
}
.row + .row {
margin-top: 1rem;
}
.square {
aspect-ratio: 1;
width: min(25%, 5rem);
}
/* Initial base color */
.bg-blue {
background-color: blue;
}
/* 50% blue, 50% white */
.bg-blue-light {
background-color: color-mix(in srgb, blue, white);
}
/* 25% blue, 75% white */
.bg-blue-lighter {
background-color: color-mix(in srgb, blue, white 75%);
}
/* 50% blue, 50% black */
.bg-blue-dark {
background-color: color-mix(in srgb, blue, black);
}
/* 25% blue, 75% black */
.bg-blue-darker {
background-color: color-mix(in srgb, blue, black 75%);
}
透過將 color-mix() 值儲存為自定義屬性,我們可以在程式碼中重用它們。當我們需要建立品牌主顏色的淺色或深色變體時,這種方法很有用。
例如,下面的程式碼演示瞭如何使用 --brand 自定義屬性建立品牌顏色變體。
:root {
--brand: rgb(0 0 255);
--brand-light: color-mix(in srgb, var(--brand), white);
--brand-lighter: color-mix(in srgb, var(--brand), white 75%);
--brand-dark: color-mix(in srgb, var(--brand), black);
--brand-darker: color-mix(in srgb, var(--brand), black 75%);
}
我們也可以透過混合 transparent 來建立不同透明度的變體
:root {
--brand: rgb(0 0 255);
--brand-alpha-50: color-mix(in srgb, blue, transparent);
--brand-alpha-75: color-mix(in srgb, blue 75%, transparent);
}
Una Kravets 的文章 使用 color-mix() 建立透明度變體 進行了更詳細的闡述。
讓我們將 color-mix() 自定義屬性應用於一個實際案例:為一個簡單的按鈕設定樣式。首先,我們為主要的基色和輔助色定義自定義屬性。作為額外的好處,我們使用 color-mix() 為輔助色將基色與 pink 混合。
:root {
--brand: rgb(0 0 255);
--brand-light: color-mix(in srgb, blue, white);
--secondary: color-mix(in srgb, var(--brand), pink);
--secondary-light: color-mix(in srgb, var(--secondary), white);
}
接下來,我們將這些顏色應用於主按鈕和輔助按鈕變體,並在懸停狀態下使用較淺的顏色變體。
<button>Primary button</button>
<button class="secondary">Secondary button</button>
* {
box-sizing: border-box;
}
:root {
--brand: rgb(0 0 255);
--brand-light: color-mix(in srgb, blue, white);
--secondary: color-mix(in srgb, var(--brand), pink);
--secondary-light: color-mix(in srgb, var(--secondary), white);
}
button {
appearance: none;
border: none;
padding: 1rem 1.5rem;
border-radius: 0.65rem;
}
button {
background-color: var(--brand);
color: white;
}
button:where(:hover, :focus) {
background-color: var(--brand-light);
}
button.secondary {
background-color: var(--secondary);
}
button.secondary:where(:hover, :focus) {
background-color: var(--secondary-light);
}
我們不限於僅在根級別定義自定義屬性。例如,我們可以為元件的基色設定自定義屬性,並在元件的樣式中使用 color-mix() 建立此基色的變體。對於輔助元件變體,我們可以簡單地應用不同的基色。下面將對此進行說明。
<div class="card">
<h3>Card title</h3>
<p>Hello</p>
</div>
<div class="card secondary">
<h3>Card title</h3>
<p>Hello</p>
</div>
* {
box-sizing: border-box;
}
body {
font-family: "Helvetica", sans-serif;
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
}
.card {
--color: blue;
background: color-mix(in srgb, var(--color), white 80%);
border-top: 5px solid var(--color);
padding: 1rem;
}
.secondary {
--color: deeppink;
}
這是一個將此概念應用於各種 UI 元件的演示。
<div class="wrapper">
<button>Primary button</button>
<button class="button--secondary">Secondary button</button>
<blockquote>
<p>I am a simple quote</p>
<cite>Ms A. Person</cite>
</blockquote>
<blockquote class="blockquote--secondary">
<p>I am a simple quote</p>
<cite>Ms A. Person</cite>
</blockquote>
<div class="grid">
<article class="card">
<img
src="https://images.unsplash.com/photo-1422205512921-12dac7b3b603?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MDcyMzE0MzJ8&ixlib=rb-4.0.3&q=80&w=400"
alt="Pink flowers in a meadow" />
<div class="card__content">
<h3>Card heading</h3>
<a href="#0">Find out more</a>
</div>
</article>
<article class="card card--secondary">
<img
src="https://images.unsplash.com/photo-1422205512921-12dac7b3b603?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3wzMjM4NDZ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3MDcyMzE0MzJ8&ixlib=rb-4.0.3&q=80&w=400"
alt="Pink flowers in a meadow" />
<div class="card__content">
<h3>Card heading</h3>
<a href="#0">Find out more</a>
</div>
</article>
</div>
</div>
* {
box-sizing: border-box;
}
:root {
--base: orchid;
--secondary: color-mix(in srgb, var(--base), lightblue 50%);
}
body {
margin: 0;
font-family: "Open Sans", sans-serif;
padding: 1rem;
}
p {
margin-block: 0;
}
img {
display: block;
width: 100%;
}
a {
color: inherit;
}
h3 {
margin-block: 0;
}
.wrapper {
max-width: 40rem;
margin: 0 auto;
}
.grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
}
button {
--color: var(--base);
appearance: none;
border: none;
padding: 1rem 1.5rem;
border-radius: 0.5rem;
background: var(--color);
}
button:where(:hover, :focus) {
background: color-mix(in srgb, var(--color), white 20%);
}
.button--secondary {
--color: var(--secondary);
}
blockquote {
--color: var(--base);
background: color-mix(in srgb, var(--color), white 75%);
padding: 1rem;
margin-inline: 0;
border-left: 5px solid var(--color);
p {
font-size: 1.5em;
}
}
cite {
display: block;
margin-top: 1rem;
font-style: normal;
}
.blockquote--secondary {
--color: var(--secondary);
}
.card {
--color: var(--base);
background: color-mix(in srgb, var(--color), white 75%);
border-radius: 0.5rem;
overflow: hidden;
}
.card--secondary {
--color: var(--secondary);
}
.card__content {
padding: 1rem;
a {
display: block;
margin-top: 1rem;
color: color-mix(in srgb, var(--color), black 40%);
}
}
雖然建立現有顏色的淺色或深色變體是 color-mix() 的常見用例,但除此之外,我們還可以透過將更暖色或更冷色混合到原始調色盤中來建立暖色和冷色變體。
在這裡,我們定義了一個初始調色盤(顏色來自 Coolors),以及我們想要混合的顏色,以使用自定義屬性建立暖色和冷色變體。
:root {
--yellow: rgb(221 215 141);
--peach: rgb(220 191 133);
--chocolate: rgb(139 99 92);
--khaki: rgb(96 89 77);
--grey: rgb(147 162 155);
--mix-warm: red;
--mix-cool: blue;
}
.palette > div {
--color: var(--yellow);
&:nth-child(2) {
--color: var(--peach);
}
&:nth-child(3) {
--color: var(--chocolate);
}
&:nth-child(4) {
--color: var(--khaki);
}
&:nth-child(5) {
--color: var(--grey);
}
}
然後,我們使用自定義屬性將第二種顏色混合到原始基色中,並指定比例。我們還指定了預設值,這樣如果未為 --mix 提供值,將使用原始基色。
.palette > div {
background: color-mix(
in srgb,
var(--color),
var(--mix, var(--color)) var(--amount, 10%)
);
}
透過這種方式,我們可以混合不同的顏色並將它們應用於整個調色盤。
.cool {
--mix: var(--mix-cool);
}
.cool--20 {
--amount: 20%;
}
.warm {
--mix: var(--mix-warm);
}
.warm--20 {
--amount: 20%;
}
<ul>
<li class="item">
<p>Original color palette</p>
<div class="palette">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</li>
<li class="item">
<p>red 10%</p>
<div class="palette warm">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</li>
<li class="item">
<p>red 20%</p>
<div class="palette warm warm--20">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</li>
<li class="item">
<p>blue 10%</p>
<div class="palette cool">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</li>
<li class="item">
<p>blue 20%</p>
<div class="palette cool cool--20">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</li>
<li class="item">
<p>white 40%</p>
<div class="palette white" style="--amount: 40%">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</li>
</ul>
* {
box-sizing: border-box;
}
:root {
--yellow: rgb(221, 215, 141);
--peach: rgb(220, 191, 133);
--chocolate: rgb(139, 99, 92);
--khaki: rgb(96, 89, 77);
--grey: rgb(147, 162, 155);
--mix-warm: red;
--mix-cool: blue;
}
body {
margin: 0;
padding: 1rem;
font-family: "Helvetica", sans-serif;
}
ul {
list-style: none;
padding: 0;
max-width: 900px;
margin: 0 auto;
}
.item {
display: grid;
grid-template-columns: 10rem 1fr;
gap: 1rem;
}
.palette {
display: grid;
grid-template-columns: repeat(5, 1fr);
& > div {
--color: var(--yellow);
aspect-ratio: 1;
background: var(--color);
&:nth-child(2) {
--color: var(--peach);
}
&:nth-child(3) {
--color: var(--chocolate);
}
&:nth-child(4) {
--color: var(--khaki);
}
&:nth-child(5) {
--color: var(--grey);
}
}
}
.item:not(:first-child) {
.palette > div {
background: color-mix(
in srgb,
var(--color),
var(--mix, var(--color)) var(--amount, 10%)
);
}
}
.cool {
--mix: var(--mix-cool);
}
.cool--20 {
--amount: 20%;
}
.warm {
--mix: var(--mix-warm);
}
.warm--20 {
--amount: 20%;
}
.white {
--mix: #fff;
}
在前面的部分中,我們使用 srgb(標準紅綠藍)作為 顏色插值方法。透過修改插值使用的顏色空間,我們可以極大地改變結果。顏色空間是一個複雜的主題,遠遠超出了本文的範圍,但值得注意幾個顏色空間在 color-mix() 中使用時的優缺點。
顏色插值決定一種顏色如何過渡到另一種顏色。一種視覺化此過程的好方法是使用漸變,正如 Adam Argyle 在 顏色插值 部分中所做的那樣,該部分深入探討了顏色空間和色域。
經典的 RGB 插值可能導致中間插值區域(漸變區域的中間部分)顏色渾濁,而使用 lch 或 oklch 時顏色保持鮮豔。如下圖所示,當應用於前一個示例中的暖色和冷色調色盤時,結果明顯不同。

與 srgb 和 hsl 不同,oklch 和 oklab 都是感知均勻的。用 Lea Verou 的話來說,這意味著
座標中的相同數值變化會產生相同的感知顏色差異
因此,在 color-mix() 進行顏色插值時,我傾向於優先選擇 oklch 和 oklab 顏色空間——但選擇權在你!
在極性(或圓形)顏色空間中,例如 oklch、oklab 和 hsl,我們還可以選擇顏色插值的方向。當我們以相等的比例混合兩種顏色時,結果的色相角將是兩種顏色角度之間的一半。這會因插值遵循顏色圓上的較短路徑還是較長路徑而有所不同。
color-mix(in hsl, rgb(255 88 88), rgb(86 86 255));
color-mix(in hsl longer hue, rgb(255 88 88), rgb(86 86 255));

在下面的框架中,透過混合不同顏色空間中的顏色來實驗,以觀察結果。
<div class="controls">
<label for="amount">Amount</label>
<input type="range" min="0" max="100" id="amount" />
<label for="c1">Color 1</label>
<input type="color" id="c1" value="#FF0000" data-color />
<label for="c2">Color 2</label>
<input type="color" id="c2" value="#0000FF" data-color />
<fieldset>
<p>Interpolation method</p>
<input type="radio" id="shorter" name="method" value="shorter" checked />
<label for="shorter">Shorter</label>
<input type="radio" id="longer" name="method" value="longer" />
<label for="longer">Longer</label>
</fieldset>
<fieldset>
<p>Color space</p>
<input type="radio" id="srgb" name="space" value="srgb" checked />
<label for="srgb">srgb</label>
<input type="radio" id="oklch" name="space" value="oklch" />
<label for="oklch">oklch</label>
<input type="radio" id="hsl" name="space" value="hsl" />
<label for="hsl">hsl</label>
</fieldset>
</div>
<p data-text hidden></p>
* {
box-sizing: border-box;
}
body {
--c1: red;
--c2: blue;
--amount1: 50%;
--amount2: 50%;
background: color-mix(
in var(--space, srgb),
var(--c1) var(--amount1),
var(--c2) var(--amount2)
);
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem;
min-height: 100vh;
margin: 0;
font-family: "Helvetica", sans-serif;
}
.controls {
max-width: 20rem;
padding: 1rem;
background: white;
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem;
align-items: center;
max-height: 100vh;
overflow: auto;
}
fieldset {
grid-column: 1 / span 2;
display: grid;
grid-template-columns: subgrid;
gap: 0.5rem;
padding: 0.75rem;
p {
grid-column: span 2;
margin: 0;
}
}
[data-text] {
align-self: center;
background: rgb(255 255 255 / 0.7);
padding: 1rem;
margin: 1rem;
border-radius: 0.5rem;
text-align: center;
line-height: 1.4;
}
const amountInput = document.querySelector("#amount");
const text = document.querySelector("[data-text]");
const colorInputs = document.querySelectorAll("[data-color]");
const colorSpaceInputs = [...document.querySelectorAll('[name="space"]')];
const methodInputs = [...document.querySelectorAll('[name="method"]')];
const setAmounts = (value) => {
document.body.style.setProperty("--amount1", `${value}%`);
document.body.style.setProperty("--amount2", `${100 - value}%`);
};
const getColorSpace = () => {
const space = colorSpaceInputs.find((el) => el.checked).value;
if (space === "srgb") return space;
const method = methodInputs.find((el) => el.checked).value;
return `${space} ${method} hue`;
};
const setTextContent = (value) => {
const c1 = getComputedStyle(document.body).getPropertyValue("--c1");
const c2 = getComputedStyle(document.body).getPropertyValue("--c2");
text.hidden = false;
text.innerHTML = `
<code>color-mix(in ${getColorSpace()}, ${c1} ${value}%, ${c2} ${
100 - value
}%)</code>`;
};
amountInput.addEventListener("input", (e) => {
const { value } = e.target;
setAmounts(value);
setTextContent(value);
});
colorInputs.forEach((el) => {
el.addEventListener("input", (e) => {
const { id, value } = e.target;
document.body.style.setProperty(`--${id}`, value);
setTextContent(amountInput.value);
});
});
const setColorSpace = () => {
document.body.style.setProperty("--space", getColorSpace());
};
colorSpaceInputs.forEach((el) => {
el.addEventListener("change", (e) => {
const { value } = e.target;
setColorSpace(value);
setTextContent(amountInput.value);
});
});
methodInputs.forEach((el) => {
el.addEventListener("change", () => {
const space = colorSpaceInputs.find((el) => el.checked).value;
setColorSpace();
setTextContent(amountInput.value);
});
});
setAmounts(amountInput.value);
setTextContent(amountInput.value);
setColorSpace();
自 2023 年中期以來,所有現代瀏覽器都支援 color-mix() 函式。一如既往,請記住並非所有使用者都會擁有最新的瀏覽器。確保每個人都能獲得可用體驗的一種方法是設定初始顏色值。不支援 color-mix() 的瀏覽器將忽略第二個宣告。
div {
/* First declaration is fallback for browsers that do not support color-mix() */
background: rgb(150 0 255);
background: color-mix(in srgb, blue, red);
}
或者,我們可以使用功能查詢來檢測瀏覽器是否支援 color-mix() 並提供相應的樣式
.card {
background: lightblue;
}
@supports (color-mix(in srgb, blue, white)) {
.card {
--color: blue;
background: color-mix(in srgb, var(--color), white 80%);
border-top: 5px solid var(--color);
}
}
如果您使用 PostCSS,有一個 PostCSS 外掛,它捆綁在 postcss-preset-env 中。這個外掛允許您編寫 color-mix() 函式,而不必擔心瀏覽器支援,因為您的程式碼在構建時會自動轉換為跨瀏覽器相容的 CSS。例如
.some-element {
background-color: color-mix(in srgb, red, blue);
}
會變成
.some-element {
background-color: rgb(128 0 128);
}
值得注意的是,這僅適用於靜態值,而不適用於自定義屬性或 JavaScript 中設定的動態值。
我們已經瞭解瞭如何使用 color-mix() 建立顏色變體,以及該函式如何與自定義屬性結合使用以用於專案和設計系統。由於瀏覽器支援已經相當不錯,我們可以期待 Web 上處理顏色的新時代!