Sass 色彩空間&廣色域 顏色

釋出於 2024 年 9 月 11 日,作者:Miriam Suzanne

廣色域顏色即將登陸 Sass!

我應該澄清一下。像 oklch(…)color(display-p3 …) 這樣的廣色域 CSS 顏色格式自 2023 年 5 月以來已在所有主流瀏覽器中可用。但在此之前,Sass 中就允許使用這些新的顏色格式。這是我最喜歡的 Sass 功能之一:大多數新的 CSS都能正常工作,無需任何“官方”支援或更新。當 Sass 遇到未知的 CSS 時,它會將該程式碼傳遞給瀏覽器。並非所有內容都需要 預處理。

通常,這正是我們所需要的。當級聯層和容器查詢在瀏覽器中推出時,Sass 無需做任何額外操作。但是新的 CSS 顏色格式略有不同。由於顏色是 Sass 中的一級資料型別,因此我們並不總是希望按原樣傳遞它們。我們通常希望在顏色傳遞到瀏覽器之前對其進行操作和管理。 瀏覽器。

已經瞭解所有關於色彩空間的知識了嗎?跳到新的 Sass 功能

顏色格式的權衡顏色格式的權衡 永久連結

CSS 從歷史上看一直侷限於 sRGB 顏色格式,它們共享兩個主要 特徵

  • 它們使用底層的 RGB 顏色模型 來表示&透過控制redgreenblue 光的相對量來以數學方式操作顏色。
  • 它們只能表示 sRGB 色域 中的顏色——自 20 世紀 90 年代中期以來,彩色顯示器上可以顯示的預設顏色範圍。

清晰的色域邊界清晰的色域邊界 永久連結

之前在 CSS 中可用的格式——命名顏色(例如 red)、hex 顏色(例如 #f00)和顏色函式(例如 rgb()/rgba()hsl()/hsla(),以及最近的 hwb())——都是描述 sRGB 顏色的方法。命名顏色是特殊的,但其他格式使用“座標”系統,就像色域的顏色被投影到 3d 空間中一樣

sRGB gamut rendered in sRGB space forms a rainbow colored cube sRGB gamut rendered in hsl space forms a rainbow-edged cylinder with black at the bottom and white at the top sRGB gamut rendered in hwb space forms a rainbow-core top surface with a black-to-gray bottom and gray-to-white outside edge
使用 Isaac Muse 的 ColorAide 生成的影像。

看看那些漂亮的幾何形狀!RGB 為我們提供了彩虹立方體,而 HSLHWB(及其“極性”hue 通道)將相同顏色排列成圓柱體。清晰的邊界使我們能夠輕鬆地(以數學方式)知道哪些顏色在色域內超出色域。在 rgb() 中,我們使用 0-255 的值。該範圍內的任何內容都將在立方體內部,但如果某個通道低於 0 或高於 255,我們就不再位於 sRGB 色域內。在 hsl()hwb() 中,hue 座標可以繼續圍繞圓圈旋轉而不會達到逃逸速度,但 saturationlightnesswhitenessblackness 通道則從 0-10%-100% 清晰地變化。同樣,任何超出該範圍的值都超出了顏色 空間。

匹配人類感知匹配人類感知 永久連結

但這種簡單性也帶來了侷限性。最明顯的是顯示器不斷改進。如今,許多顯示器可以顯示超出 sRGB 的顏色,尤其是在擴充套件可用亮綠色的範圍方面。如果我們只是用新的可用顏色擴充套件我們的形狀,那麼我們就不再處理乾淨的 幾何圖形了!

display-p3 gamut rendered in sRGB space adds unequal red and green horns to the sRGB cube display-p3 gamut rendered in hsl space creates a boot-like bulge of green near the base of the hsl cylinder

sRGB 格式的清晰邊緣和乾淨的數學運算之所以成為可能,是因為我們確切地知道可以顯示哪些顏色,並且我們將這些顏色完美地排列到一個盒子中。但是人類的顏色感知並非如此明確,並且與市場上任何顯示器的色域都不完全一致。當我們嘗試根據人類感知而不是簡單的數學運算來均勻間隔所有相同的顏色時,我們會得到一個具有彎曲邊緣的完全不同的形狀。這是 oklch 空間中的 display-p3 色域

display-p3 gamut rendered in oklch space forms a skewed cube with a conic black base

當我們比較 hsloklch 中相同“亮度”的顏色時,實際差異尤其明顯。人類認為黃色色調比藍色色調更亮。透過將其縮放以適合相同範圍,hsl 為我們提供了比 藍色亮得多的黃色

on the left a blue and much brighter yellow, on the right our yellow is much darker to match the blue tone

新的 CSS 格式為我們提供了選擇新的 CSS 格式為我們提供了選擇 永久連結

展望未來,我們可以用兩種方式處理廣色域 顏色

  • 將越來越大的色域重新擬合到簡單的座標中,拉伸顏色以保留乾淨的幾何 邊界。
  • 保持其感知上均勻的間距,而不考慮特定的 色域。

一方面,清晰的邊界使我們能夠輕鬆地保持在可用顏色的範圍內。如果沒有這些邊界,很容易意外地請求實際上不存在的顏色。另一方面,我們希望這些顏色被其他人感知到——我們需要使事物看起來一致,並具有足夠的對比度才能 可讀。

CSS 顏色模組級別 4 定義了許多新的 CSS 顏色格式。其中一些保持了對特定顏色空間的幾何訪問。與更熟悉的 rgb()hsl() 函式一樣,較新的 hwb() 函式仍然使用 huewhitenessblackness 通道描述 sRGB 色域中的顏色。這是一個有趣的格式,而且我之前寫過關於它的文章 

其餘的色域邊界空間可以使用 color(<space> <3-channels> / <alpha>) 函式獲得。使用該語法,我們可以在 sRGBsrbg-lineardisplay-p3(現代顯示器常見)、a98-rgbprophoto-rgbrec2020 中定義顏色。這些都將指定的色域對映到 0-10%-100% 的(立方)座標範圍內。非常 乾淨。

在相同的 color() 函式中,我們還可以訪問“裝置無關”(且無色域)的 xyz 顏色空間——通常用作在不同顏色模型之間轉換的國際基線。我在這裡不會介紹白點,但我們可以明確指定 xyz-d65(預設值),或者使用 xyz-d50 代替。

xyz 向外擴充套件,我們得到了一些新的理論上無界的顏色格式——優先考慮感知上均勻的分佈而不是乾淨的幾何形狀。這些可以透過其自身的函式獲得,包括 lab()lightnessab)和 lch()lightnesschromahue)以及每個函式的較新的“ok”版本——oklab()oklch()。如果您想了解這些格式的完整歷史,Eric Portis 寫了一篇很棒的 解釋

TL;DR 優先順序最高的新格式TL;DR 優先順序最高的新格式 永久連結

對於色彩專家來說,擁有所有這些靈活性非常棒。對於我們其他人來說,有一些突出的 格式

  • color(display-p3 …) 提供了對更廣泛的顏色色域的訪問,這些顏色在許多現代顯示器上可用,同時保持了一組清晰的色域 邊界。
  • oklch(…) 是最直觀和感知上最均勻的空間,是 hsl(…) 的較新替代方案——chromasaturation 非常相似。但是這裡幾乎沒有防護欄,很容易最終超出任何螢幕可能顯示的色域。座標系仍在描述一個圓柱體,但人類感知和顯示技術的邊緣並沒有整齊地對映到該 空間中。
  • 對於過渡和漸變,如果我們想直接在色調之間進行轉換(而不是圍繞色輪),oklab(…) 是一個不錯的線性選項。通常,兩個色域內顏色的過渡或漸變將保持在色域內——但當我們處理飽和度或 亮度的極端情況時,我們不能總是依賴這一點。

CSS 中的 Sass 顏色函式CSS 中的 Sass 顏色函式 永久連結

Sass 現在接受所有新的 CSS 格式,並將它們視為我們可以操作、混合、轉換和檢查的一級顏色。這些函式在 全域性範圍內可用

  • lab()oklab()lch()oklch()
  • 使用 sRGBsrgb-lineardisplay-p3a98-rgbprophoto-rgbrec2020xyzxyz-d65xyz-d50 顏色空間的 color()
  • hwb()(Sass 之前有一個 color.hwb() 函式,現在已棄用,取而代之的是全域性 函式)

Sass 顏色函式使用與 CSS 函式相同的語法,這意味著給定的顏色可以用各種不同的空間表示。例如,這些都是相同的 顏色

遊樂場

SCSS 語法

@debug MediumVioletRed;
@debug #C71585;
@debug hsl(322.2 80.91% 43.14%);
@debug oklch(55.34% 0.2217 349.7);
@debug color(display-p3 0.716 0.1763 0.5105);
遊樂場

Sass 語法

@debug MediumVioletRed
@debug #C71585
@debug hsl(322.2 80.91% 43.14%)
@debug oklch(55.34% 0.2217 349.7)
@debug color(display-p3 0.716 0.1763 0.5105)

Sass 顏色保留其空間Sass 顏色保留其空間 永久連結

從歷史上看,CSS 和 Sass 都將不同的顏色空間視為可互換的。當所有顏色格式使用相同的底層模型描述相同的顏色色域時,您可以使用 hsl() 語法提供顏色,解析器可以急切地將其轉換為 rgb(),而不會冒任何資料丟失的風險。對於現代顏色 空間,情況已不再如此。

通常,在給定空間中定義的任何顏色都將保留在該空間中,並以該空間的形式發出。空間由使用的函式定義,可以是傳遞給 color() 的名稱空間之一,也可以是函式名稱(例如,使用 lab() 函式定義的顏色為 lab)。

但是,rgbhslhwb 空間被認為是“傳統空間”,為了向後相容,通常會對其進行特殊處理。傳統顏色仍以最向後相容的可用格式發出。這與 CSS 本身的向後相容 行為相匹配。使用十六進位制表示法或 CSS 顏色名稱定義的顏色也被視為傳統 rgb 顏色空間的一部分。

Sass 提供了各種用於檢查和處理這些顏色 空間的工具

  • 我們可以使用 color.space($color) 檢查顏色的空間
  • 我們可以使用 color.is-legacy($color) 查詢顏色是否在傳統空間中
  • 我們可以使用 color.to-space($color, $space) 將顏色從一個空間轉換為另一個空間

所有這些函式都由內建的 Sass 顏色模組 提供

遊樂場

SCSS 語法

@use 'sass:color';
$brand: MediumVioletRed;

// results: rgb, true
@debug color.space($brand);
@debug color.is-legacy($brand);

// result: oklch(55.34% 0.2217 349.7)
@debug color.to-space($brand, 'oklch');

// results: oklch, false
@debug color.space($brand);
@debug color.is-legacy($brand);
遊樂場

Sass 語法

@use 'sass:color'
$brand: MediumVioletRed

// results: rgb, true
@debug color.space($brand)
@debug color.is-legacy($brand)

// result: oklch(55.34% 0.2217 349.7)
@debug color.to-space($brand, 'oklch')

// results: oklch, false
@debug color.space($brand)
@debug color.is-legacy($brand)

一旦我們在顏色空間之間進行轉換,我們就不會再認為這些顏色是相等的。但我們可以使用color.same()函式詢問它們是否會渲染為“相同”的顏色。

遊樂場

SCSS 語法

@use 'sass:color';
$orange-rgb: #ff5f00;
$orange-oklch: oklch(68.72% 20.966858279% 41.4189852913deg);

// result: false
@debug $orange-rgb == $orange-oklch;

// result: true
@debug color.same($orange-rgb, $orange-oklch);
遊樂場

Sass 語法

@use 'sass:color'
$orange-rgb: #ff5f00
$orange-oklch: oklch(68.72% 20.966858279% 41.4189852913deg)

// result: false
@debug $orange-rgb == $orange-oklch

// result: true
@debug color.same($orange-rgb, $orange-oklch)

我們可以使用color.channel()檢查顏色的各個通道。預設情況下,它只支援顏色自身空間中可用的通道,但我們可以傳遞$space引數以在轉換為給定空間後返回通道值的數值。

遊樂場

SCSS 語法

@use 'sass:color';
$brand: hsl(0 100% 25.1%);

// result: 25.1%
@debug color.channel($brand, "lightness");

// result: 37.67%
@debug color.channel($brand, "lightness", $space: oklch);
遊樂場

Sass 語法

@use 'sass:color'
$brand: hsl(0 100% 25.1%)

// result: 25.1%
@debug color.channel($brand, "lightness")

// result: 37.67%
@debug color.channel($brand, "lightness", $space: oklch)

CSS還引入了“無能量”和“缺失”顏色通道的概念。例如,飽和度為0%hsl顏色將始終為灰度。在這種情況下,我們可以認為色相通道是無能量的。更改其值不會對生成的顏色產生任何影響。Sass允許我們使用color.is-powerless()函式詢問某個通道是否為無能量的。

遊樂場

SCSS 語法

@use 'sass:color';
$gray: hsl(0 0% 60%);

// result: true, because saturation is 0
@debug color.is-powerless($gray, "hue");

// result: false
@debug color.is-powerless($gray, "lightness");
遊樂場

Sass 語法

@use 'sass:color'
$gray: hsl(0 0% 60%)

// result: true, because saturation is 0
@debug color.is-powerless($gray, "hue")

// result: false
@debug color.is-powerless($gray, "lightness")

更進一步,CSS還允許我們顯式地將某個通道標記為“缺失”或未知。如果我們將像gray這樣的顏色轉換為oklch這樣的顏色空間,則可能會自動發生這種情況——我們沒有任何關於色相的資訊。我們還可以使用none關鍵字顯式地建立具有缺失通道的顏色,並使用color.is-missing()函式檢查顏色通道是否缺失。

遊樂場

SCSS 語法

@use 'sass:color';
$brand: hsl(none 100% 25.1%);

// result: false
@debug color.is-missing($brand, "lightness");

// result: true
@debug color.is-missing($brand, "hue");
遊樂場

Sass 語法

@use 'sass:color'
$brand: hsl(none 100% 25.1%)

// result: false
@debug color.is-missing($brand, "lightness")

// result: true
@debug color.is-missing($brand, "hue")

CSS一樣,Sass會在有意義的地方保留缺失的通道,但在需要通道值時將其視為0

操作 Sass 顏色操作 Sass 顏色 連結

現有的color.scale()color.adjust()color.change()函式將繼續按預期工作。預設情況下,所有顏色操作都在顏色提供的空間中執行。但我們現在也可以為轉換指定顯式顏色空間。

遊樂場

SCSS 語法

@use 'sass:color';
$brand: hsl(0 100% 25.1%);

// result: hsl(0 100% 43.8%)
@debug color.scale($brand, $lightness: 25%);

// result: hsl(5.76 56% 45.4%)
@debug color.scale($brand, $lightness: 25%, $space: oklch);
遊樂場

Sass 語法

@use 'sass:color'
$brand: hsl(0 100% 25.1%)

// result: hsl(0 100% 43.8%)
@debug color.scale($brand, $lightness: 25%)

// result: hsl(5.76 56% 45.4%)
@debug color.scale($brand, $lightness: 25%, $space: oklch)

請注意,即使調整是在不同的空間中執行的,返回的顏色仍然以原始顏色空間返回。這樣,我們就可以開始在oklch等更高階的顏色空間中使用它們,而無需依賴瀏覽器支援這些格式。

現有的color.mix()函式在兩個顏色都位於傳統顏色空間中時也將保持現有行為。傳統混合始終在rgb空間中完成。我們可以使用新的$method引數選擇其他混合技術,該引數旨在匹配CSS用於描述插值方法的規範——用於CSS漸變、濾鏡、動畫和過渡,以及新的CSS color-mix()函式。

對於傳統顏色,該方法是可選的。但對於非傳統顏色,則需要一個方法。在大多數情況下,該方法可以簡單地是一個顏色空間名稱。但當我們使用具有“極座標色相”通道的顏色空間(例如hslhwblchoklch)時,我們還可以指定要在色輪上移動的方向較短色相較長色相遞增色相遞減色相

遊樂場

SCSS 語法

@use 'sass:color';

// result: #660099
@debug color.mix(red, blue, 40%);

// result: rgb(176.2950613593, -28.8924497904, 159.1757183525)
@debug color.mix(red, blue, 40%, $method: lab);

// result: rgb(-129.55249236, 149.0291922672, 77.9649510422)
@debug color.mix(red, blue, 40%, $method: oklch longer hue);
遊樂場

Sass 語法

@use 'sass:color'

// result: #660099
@debug color.mix(red, blue, 40%)

// result: rgb(176.2950613593, -28.8924497904, 159.1757183525)
@debug color.mix(red, blue, 40%, $method: lab)

// result: rgb(-129.55249236, 149.0291922672, 77.9649510422)
@debug color.mix(red, blue, 40%, $method: oklch longer hue)

在這種情況下,混合中的第一個顏色被視為“原點”顏色。與上面的其他函式一樣,我們可以使用不同的空間進行混合,但結果始終以該原點顏色空間返回。

處理色域邊界處理色域邊界 連結

那麼當您超出給定顯示器的色域時會發生什麼?瀏覽器仍在爭論細節,但每個人都同意我們必須顯示某些東西

  • 目前,瀏覽器將每種顏色轉換為紅色綠色藍色通道以進行顯示。如果這些通道中的任何一個對於給定的螢幕來說過高或過低,則它們會在允許的最高或最低值處裁剪。這通常被稱為“通道裁剪”。它使數學變得簡單,但如果某些通道比其他通道裁剪得更多,則可能會對色相亮度產生奇怪的影響。
  • CSS規範指出,保持亮度應該具有最高優先順序,並提供了一種演算法來降低彩度,直到顏色在色域內。這對於保持文字的可讀性非常有用,但它會給瀏覽器帶來更多工作,並且當顏色突然失去其活力時可能會讓人感到意外。
  • 在一種折衷方案上取得了一些進展,即降低彩度以使顏色位於rec2020色域內,然後從那裡進行裁剪。

由於瀏覽器的行為仍然不可靠,並且某些顏色空間(咳咳 oklch)很容易讓我們超出任何可用的色域,因此在Sass中進行一些色域管理可能會有所幫助。

我們可以使用color.is-in-gamut()測試特定顏色是否在給定色域內。與我們其他的顏色函式一樣,這將預設為顏色定義所在的空間,但我們可以提供$space引數以針對不同的色域進行測試。

遊樂場

SCSS 語法

@use 'sass:color';
$extra-pink: color(display-p3 0.951 0.457 0.7569);

// result: true, for display-p3 gamut
@debug color.is-in-gamut($extra-pink);

// result: false, for srgb gamut
@debug color.is-in-gamut($extra-pink, $space: srgb);
遊樂場

Sass 語法

@use 'sass:color'
$extra-pink: color(display-p3 0.951 0.457 0.7569)

// result: true, for display-p3 gamut
@debug color.is-in-gamut($extra-pink)

// result: false, for srgb gamut
@debug color.is-in-gamut($extra-pink, $space: srgb)

我們還可以使用color.to-gamut()函式顯式地移動顏色,使其位於特定色域內。由於表上有幾個選項,並且不清楚長期預設的CSS將使用什麼,因此此函式目前需要一個顯式的$method引數。當前選項為clip(如瀏覽器當前應用的)或local-minde(如當前指定的)。

遊樂場

SCSS 語法

@use 'sass:color';
$extra-pink: oklch(90% 90% 0deg);

// result: oklch(68.3601568298% 0.290089749 338.3604392249deg)
@debug color.to-gamut($extra-pink, srgb, clip);

// result: oklch(88.7173946522% 0.0667320674 355.3282956627deg)
@debug color.to-gamut($extra-pink, srgb, local-minde);
遊樂場

Sass 語法

@use 'sass:color'
$extra-pink: oklch(90% 90% 0deg)

// result: oklch(68.3601568298% 0.290089749 338.3604392249deg)
@debug color.to-gamut($extra-pink, srgb, clip)

// result: oklch(88.7173946522% 0.0667320674 355.3282956627deg)
@debug color.to-gamut($extra-pink, srgb, local-minde)

所有傳統和RGB樣式空間都表示有界顏色的色域。由於將顏色對映到色域是一個有損過程,因此通常應將其留給瀏覽器處理或謹慎操作。因此,即使在轉換為有界顏色空間時,Sass也會保留色域外的通道值。

傳統瀏覽器需要srgb色域中的顏色。但是,大多數現代顯示器都支援更寬的display-p3色域。

已棄用的函式已棄用的函式 連結

許多現有函式僅對傳統顏色有意義,因此正在被棄用,取而代之的是像color.channel()color.adjust()這樣的顏色空間友好函式。最終,這些函式將完全從Sass中刪除,但所有相同的功能仍然可以在更新的函式中使用。

  • color.red()
  • color.green()
  • color.blue()
  • color.hue()
  • color.saturation()
  • color.lightness()
  • color.whiteness()
  • color.blackness()
  • adjust-hue()
  • saturate()
  • desaturate()
  • transparentize()/fade-out()
  • opacify()/fade-in()
  • lighten()/darken()

我們添加了一個遷移工具,以便自動將這些傳統函式轉換為顏色空間友好的函式。

$ sass-migrator color --migrate-deps <path/to/style.scss>