CSS Colors
撰写时间:2025-03-01
修订时间:2025-04-10
Color Names
下面是CSS Color Module Level 4 所定义的命名颜色,同时换算为多种最常用的格式。
table > thead > tr > th {
position: sticky;
top: 0;
z-index: 1;
line-height: 3em;
}
table > tbody > tr > td:nth-child(2) {
display: block;
box-sizing: border-box;
margin: 1em;
width: 50px;
aspect-ratio : 1.618 / 1;
border-width: 0;
border-radius: 0.3em;
}
table > tbody > tr > td:nth-child(1) {
text-align: center;
}
import { GLColors, COLOR_NAMES } from '/js/esm/GLColors.js';
import { DOMUtils } from '/js/esm/DOMUtils.js';
import { ColorUtils } from '/js/esm/ColorUtils.js';
function genTable() {
let ths = ['Index', 'Color Block', 'Color Name', 'Hex', 'Decimal', 'Clamped RGB', 'HSL'];
let cells = [];
let index = 1;
for (let colorName in COLOR_NAMES) {
let rowData = [];
rowData.push(index++);
rowData.push('');
rowData.push(colorName.toLowerCase());
rowData.push(COLOR_NAMES[colorName]);
let decimal = ColorUtils.HexToDecimal(COLOR_NAMES[colorName]);
rowData.push(decimal.join(', '));
let clampedRGB = GLColors.FromFullHex(COLOR_NAMES[colorName]);
clampedRGB.splice(-1, 1);
let twoDigitsArr = clampedRGB.map(ele => ele.toFixed(2));
rowData.push(twoDigitsArr.join(', '));
let hslArr = ColorUtils.ClampedRgbToHsl(...clampedRGB);
let [h, s, l] = hslArr;
h = parseInt(h);
s = parseInt(s) + '%';
l = parseInt(l) + '%';
rowData.push(h + ', ' + s + ', ' + l);
cells.push(rowData);
}
let str = DOMUtils.GenTableStr(ths, cells);
document.body.insertAdjacentHTML('afterbegin', str);
}
function styleTable() {
let colorValues = Object.values(COLOR_NAMES);
let table = document.querySelector('table');
let firstTds = table.querySelectorAll('tbody tr td:nth-child(2)');
console.log(firstTds);
firstTds.forEach((td, idx) => {
td.style.backgroundColor = colorValues[idx];
});
}
genTable();
styleTable();
HSL
HSL 颜色方案 (color scheme) 使用色相 (hue ), 饱和度 (saturation ), 及亮度 (lightness ) 此3 种要素来表示一个颜色,与我们日常描绘一种颜色时所使用的颜色、鲜艳度、亮度的习惯比较吻合。
hsl 函数的参数如下:
hsl(hue, saturation, lightness);
对于hsl 函数中各参数的数据类型,可使用以下2 种表达方式:
hsl(120deg, 80%, 20%);
hsl(230, 30%, 90%);
后面两个成分应使用百分比的格式,否则浏览器可能无法正确解析。
下面代码演示了hsl 与rgb 两种函数各参数的实时转换。
import { ColorUtils } from "/js/esm/ColorUtils.js";
let hslModal = {
hue: 215,
saturation: 50,
lightness: 50
};
let hslHueEle = document.getElementById('hsl-hue');
let hslSaturationEle = document.getElementById('hsl-saturation');
let hslLightnessEle = document.getElementById('hsl-lightness');
let clampedRgbREle = document.getElementById('clamped-rgb-r');
let clampedRgbGEle = document.getElementById('clamped-rgb-g');
let clampedRgbBEle = document.getElementById('clamped-rgb-b');
let rgbREle = document.getElementById('rgb-r');
let rgbGEle = document.getElementById('rgb-g');
let rgbBEle = document.getElementById('rgb-b');
updateView();
function updateView(except) {
hslHueEle.value = hslModal.hue;
hslSaturationEle.value = hslModal.saturation;
hslLightnessEle.value = hslModal.lightness;
let clampedRgbArr = ColorUtils.HslToClampedRgb(hslModal.hue, hslModal.saturation, hslModal.lightness);
if (except !== 'clampedR') {
clampedRgbREle.value = clampedRgbArr[0].toFixed(2);
}
if (except !== 'clampedG') {
clampedRgbGEle.value = clampedRgbArr[1].toFixed(2);
}
if (except !== 'clampedB') {
clampedRgbBEle.value = clampedRgbArr[2].toFixed(2);
}
let rgbArr = ColorUtils.HslToRgb(hslModal.hue, hslModal.saturation, hslModal.lightness);
if (except !== 'r') {
rgbREle.value = rgbArr[0];
}
if (except !== 'g') {
rgbGEle.value = rgbArr[1];
}
if (except !== 'b') {
rgbBEle.value = rgbArr[2];
}
let colorBlock = document.getElementById('color-block');
colorBlock.style.backgroundColor = `hsl(${hslModal.hue}deg, ${hslModal.saturation}%, ${hslModal.lightness}%)`;
}
hslHueEle.oninput = (evt) => {
hslModal.hue = parseInt(evt.target.value);
updateView();
};
hslSaturationEle.oninput = (evt) => {
hslModal.saturation = parseInt(evt.target.value);
updateView();
};
hslLightnessEle.oninput = (evt) => {
hslModal.lightness = parseInt(evt.target.value);
updateView();
};
clampedRgbREle.oninput = (evt) => {
updateModalFromRGBView('clamped');
updateView('clampedR');
};
clampedRgbGEle.oninput = (evt) => {
updateModalFromRGBView('clamped');
updateView('clampedG');
};
clampedRgbBEle.oninput = (evt) => {
updateModalFromRGBView('clamped');
updateView('clampedB');
};
rgbREle.oninput = (evt) => {
updateModalFromRGBView();
updateView('r');
};
rgbGEle.oninput = (evt) => {
updateModalFromRGBView();
updateView('g');
};
rgbBEle.oninput = (evt) => {
updateModalFromRGBView();
updateView('b');
};
function updateModalFromRGBView(rgbType) {
let r, g, b;
let h, s, l;
if (rgbType === 'clamped') {
r = parseFloat(clampedRgbREle.value);
g = parseFloat(clampedRgbGEle.value);
b = parseFloat(clampedRgbBEle.value);
[h, s, l] = ColorUtils.ClampedRgbToHsl(r, g, b);
} else {
r = parseFloat(rgbREle.value);
g = parseFloat(rgbGEle.value);
b = parseFloat(rgbBEle.value);
[h, s, l] = ColorUtils.RgbToHsl(r, g, b);
}
hslModal.hue = Math.round(h);
hslModal.saturation = Math.round(s);
hslModal.lightness = Math.round(l);
}
form {
margin: 0em;
}
form div {
margin: 0.5em 0;
}
form label {
color: lightgreen;
display: inline-block;
min-width: 80px;
text-align: right;
}
form input[type=number] {
border: 1px solid gray;
color: #ccc;
background-color: #2b2b2b;
margin-right: 0.1em;
}
form > div > span > input {
margin: 0 0 0 1em;
}
form > div > span > span {
color: gray;
}
form > div > span > span:first-of-type {
margin-left: 0.5em;
}
form > div > span > span:last-of-type {
margin-left: 0.5em;
}
form #color-block {
width: 235px;
height: 140px;
border: 1px solid gray;
margin-left: 100px;
margin-top: 1em;
}
对于hsl 函数,如果lightness 的值为100% ,则产生纯白色;如果lightness 的值为0% ,则产生黑色。此时hue 及saturation 将不起作用。
RGB 颜色方案强调颜色通道,因此若要单独增减某个颜色通道的数值,使用该颜色方案则比较便利。
相对颜色
相对颜色功能可让我们将任意颜色转换为另外一种颜色空间的数值,然后单独操控各种颜色通道。
检测浏览器是否支持?
@supports (color: rgb(from red r g b)) {
p {
background: green;
}
}
@supports not (color: rgb(from red r g b)) {
p {
background: red;
}
}
p {
margin: 0;
padding: 1em;
}
Is relative colors supported?
改变各个通道值:
p {
--my-color: #E1C076;
color: hsl(from var(--my-color) calc(h + 20) calc(s * 0.5) calc(l * 0.8));
}
色相值增加20 ,饱和度只取50% ,亮度只取80% 。
在使用calc 函数时,使用小数点的形式来与通道值相乘;若使用百分比,则表示取自通道值域的百分比,与通道值没有关系。
p {
--my-color: green;
color: hsl(from var(--my-color) h s calc(50%));
}
from
可跟有任何有效的颜色表达式。下面使用rgb() 函数的方式:
p {
color: hsl(from rgb(128, 255, 128) h 50% 80%);
}
from
后面如果需要使用十六进制表示颜色时,表达式不能带有双引号。
p {
color: hsl(from #9933CC h 50% 80%);
}
通道值可直接用数值替换,或照抄表示通道的字母以保留:
p {
--my-color: hsl(150, 70%, 80%);
color: hsl(from var(--my-color) 220 s l);
}
将色相值使用数值220 直接替换,饱和度及亮度则予以保留。
改变不透明度:
p {
--my-color: #E1C076;
color: hsl(from var(--my-color) h s l / 90%);
}
currentcolor
可用currentcolor 来取出当前标签color 属性值。
p {
margin: 0;
padding: 1em;
color: hsl(150, 75%, 80%);
background-color: hsl(from currentcolor calc(h + 120) s l / 20%);
}
颜色融合
在特定的颜色空间,将两个颜色进行融合,得到一个新的颜色值。可指定参与融合颜色的成分占比。
:root {
--block-side: 60px;
--bg-color1: magenta;
--color1-amt: 50%;
--bg-color2: cyan;
--color2-amt: 50%;
}
#grid {
display: grid;
grid-template-areas: "src1 src2"
"dst dst";
grid-template-columns: var(--block-side) var(--block-side);
grid-template-rows: var(--block-side) var(--block-side);
gap: 0.5em;
min-height: 150px;
justify-content: center;
align-content: center;
}
#src1 {grid-area: src1}
#src2 {grid-area: src2}
#dst {grid-area: dst; justify-self: center; width: var(--block-side);}
div > div {
border-radius: 10%;
}
#src1 {background-color: var(--bg-color1);}
#src2 {background-color: var(--bg-color2);}
#dst {background-color: color-mix(in srgb, var(--bg-color1) var(--color1-amt), var(--bg-color2) var(--color2-amt));}
对比度比率
WCAG AA级对文本(包括图像中的文本),要求其对比度比率 (contrast ratio )至少为4.5:1 ,而较大字号的文本,要求其对比度比率至少为3:1 。
WCAG AAA级对文本(包括图像中的文本),要求其对比度比率至少为7:1 ,而较大字号的文本,要求其对比度比率至少为4.5:1 。
计算对比度比率的公式为:
(L1 + 0.05) / (L2 + 0.05)
其中L1 为较亮颜色的相对亮度(relative luminance ),L2 为较暗颜色的相对亮度。以sRGB 颜色方案为例,相对亮度L 的公式为:
L = 0.2126 * R + 0.7152 * G + 0.0722 * B
R , G , B 定义如下:
if RsRGB <= 0.04045 then R = RsRGB/12.92 else R = ((RsRGB+0.055)/1.055) ^ 2.4
if GsRGB <= 0.04045 then G = GsRGB/12.92 else G = ((GsRGB+0.055)/1.055) ^ 2.4
if BsRGB <= 0.04045 then B = BsRGB/12.92 else B = ((BsRGB+0.055)/1.055) ^ 2.4
常量值0.04045 在之前版本中曾为0.03928 ,后面已改为现值。
RsRGB , GsRGB , BsRGB 分别定义如下:
RsRGB = R8bit/255
GsRGB = G8bit/255
BsRGB = B8bit/255
取本页面暗黑主题的文本前景色rgb(172, 183, 196)
及背景色rgb(43, 43, 43)
为例,则对比度比率为:
function getRelativeLuminance(r, g, b) {
let R = calcChanel(r);
let G = calcChanel(g);
let B = calcChanel(b);
let L = 0.2126 * R + 0.7152 * G + 0.0722 * B;
return L;
function calcChanel(chanelBit) {
let chanel_sRGB = chanelBit / 255;
if (chanel_sRGB <= 0.04045 ) {
return chanel_sRGB / 12.92;
} else {
return Math.pow((chanel_sRGB + 0.055 / 1.055), 2.4);
}
}
}
function getContrastRatio(L1, L2) {
return (Math.max(L1, L2) + 0.05) / (Math.min(L1, L2) + 0.05);
}
let L1 = getRelativeLuminance(172, 183, 196);
let L2 = getRelativeLuminance(43, 43, 43);
let cr = getContrastRatio(L1, L2);
pc.log(cr);
因此,在暗黑主题下,其对比度比率已达到WCAG AAA级。
颜色空间的转换
转换为sRGB
在不使用相对颜色的情况下,不管使用何种颜色空间来指定颜色,浏览器经过计算后,在内部都会自动转换为以sRGB 的形式来存储其值。利用此特性,调用getComputedStyle 函数可直接得到sRGB 的结果。
#color-block {
background-color: #889966CC;
}
#container {
display: grid;
grid-template-columns: 100px auto;
grid-template-rows: 100px;
column-gap: 1em;
height: 100vh;
place-content: center;
}
#output {
align-self: center;
display: grid;
grid-template-columns: 30px 150px;
gap: 1em;
}
#output > * {
padding: 0.5em;
}
label {
color: hwb(from #336699 h 20% 0);
}
input[type=text] {
color: #eee;
background-color: #333;
border: 1px solid #555;
}
let colorBlockEle = document.getElementById("color-block");
let labelEle = document.getElementsByTagName('label')[0];
let bgColorStr = getComputedStyle(colorBlockEle).getPropertyValue('background-color');
bgColorStr = bgColorStr.replace(/(rgba?\()(.+)(\))/, '$2');
if (bgColorStr.match(/(\d{1,3}(\,\s)?){3}0\.\d/g)) {
labelEle.textContent = 'rgba:';
} else {
labelEle.textContent = 'rgb:';
}
let rgbOutput = document.getElementById("rgb-output");
rgbOutput.value = bgColorStr;
oklab
oklab 颜色空间能模拟出漫反射的光照效果。虽然其a , b 轴以色温的方式来指定色相值不太直观,但其l 轴所代表的亮度却比较接近人们对光线的感知。
下面使用滑块来控制其亮度值。
#color-block {
background-color: green;
aspect-ratio: 1 / 1;
margin: 1em;
box-sizing: border-box;
}
#container {
display: grid;
grid-template-columns: minmax(100px, 100vh) minmax(220px, 0.8fr);
height: 100vh;
place-content: center;
}
#panel {
align-self: center;
display: grid;
grid-template-columns: auto
}
input[type=range] {
-webkit-appearance: none;
background-color: transparent;
}
input[type=range]::-webkit-slider-container {
/* border: 1px solid red; */
}
input[type=range]::-webkit-slider-runnable-track {
background: hsl(51, 25%, 50%);
box-shadow: 5px 5px 5px #333, inset -5px -5px 3px #ccc;
border-radius: 0.5em;
height: 10px;
align-self: center;
display: flex;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: steelblue;
align-self: center;
}
let colorBlock = document.getElementById('color-block');
let bgColorStr = getComputedStyle(colorBlock).getPropertyValue('background-color');
let slider = document.getElementById('oklab-slider');
slider.oninput = evt => {
colorBlock.style.backgroundColor = `oklab(from ${bgColorStr} ${slider.value}% a b)`;
};
先使用具名颜色的方式来指定颜色,再利用相对颜色转换为oklab 颜色空间,最后让滑块单独控制其亮度通道值。
您是否仍在使用传统的sRGB色彩空间?
if (window.matchMedia("(color-gamut: p3)").matches) {
document.querySelector('div').textContent = 'You have a wide-range gamut display that supports DCI P3 color space.';
}
运行代码后,如果有文字出现,则意味着您有机会、但可能从未体验更加灵艳的色彩空间。因为DCI P3 的伽马 (gamut )值范围(即,能在设备上展现出能被人眼所感知的色彩范围),比传统的sRGB 要更加丰富。
div {
background-color: rgb(125, 125, 125);
width: 100px;
aspect-ratio: 1 / 1;
}
@supports (color: color(display-p3 0.0 0.0 0.0)) {
div {
background-color: color(display-p3 -0.6112 1.0079 -0.2192);
}
}
当看到亮绿色,证明您的设备支持display-p3 ;如果设备不支持,则会只看到一个sRGB 颜色空间的灰色。下面是两者能展现的色彩范围的对比图。
P3-prim-sec
上图中,实心的色球表示display-p3 的色彩范围,而半透明的色球表示sRGB 的色彩范围。前者比后者的范围要广。
Safari支持Canvas Color Spaces ,下面的代码检测其是否支持display-p3 的颜色空间。
function isSupportP3Canvas() {
let canvas = document.createElement("canvas");
try {
let context = canvas.getContext("2d", { colorSpace: "display-p3" });
return context.getContextAttributes().colorSpace == "display-p3";
} catch {
console.log('error!');
}
return false;
}
if (isSupportP3Canvas()) {
document.querySelector('p').textContent = 'Canvas supports P3 colors!';
}
在canvas 上使用display-p3 来绘制图像。
let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d", { colorSpace: "display-p3" });
let position = 0;
for (let green of [1, 0]) {
for (let blue of [1, 0]) {
for (let red of [1, 0]) {
context.fillStyle = `color(display-p3 ${red} ${green} ${blue})`;
context.fillRect(position, position, 40, 40);
position += 20;
}
}
}
下面的代码,轮流使用两个颜色空间中的绿色来绘制图像。
let canvas = document.getElementById("canvas");
let context = canvas.getContext("2d", { colorSpace: "display-p3" });
const COLORS = ["#0f0", "color(display-p3 0 1 0)"];
for (let y = 20; y < 180; y += 20) {
let color = COLORS[(y / 20) % 2];
console.log(color);
context.fillStyle = color;
context.fillRect(20, y, 160, 20);
}
在终端面板中可看到,奇行是display-p3 的绿色,偶行是sRGB 的绿色。从输出面板中可看出,display-p3 能表现的绿色比sRGB 能表现的绿色要更为鲜艳。
在Chrome中得到了同样的效果。
因此,拥抱display-p3 ,它能让我们的世界更加美不胜收。