我在构建一个 图像“表情符号化” 时遇到的一个挑战是,我需要将使用 getImageData()
获得的值的颜色空间从 RGB 更改为 HSL。我使用了按亮度和饱和度排列的表情符号数组,并且它们基于 HSL,以便平均像素颜色与表情符号最佳匹配。
在本文中,我们将研究一些对转换不透明和启用 alpha 的颜色值有用的函数。现代浏览器目前支持 RGB(A)、十六进制和 HSL(A) 颜色空间。这些函数和表示法分别是 rgb()
、rgba()
、#rgb
/#rrggbb
、#rgba
/#rrggbbaa
、hsl()
和 hsla()
。浏览器始终支持像 aliceblue
这样的内置名称。

在此过程中,我们将遇到 CSS 颜色模块的新 第 4 级 提供的一些颜色语法的使用。例如,我们现在有了带 alpha 的十六进制(#rgba
/#rrggbbaa
),并且 RGB 和 HSL 语法不再需要逗号(像 rgb(255 0 0)
和 hsl(240 100% 50%)
这样的值成为合法的!)。
截至本文撰写之时,CSS 颜色第 4 级的浏览器支持并不普遍,因此不要期望新的颜色语法在 Microsoft 浏览器或 Safari 中使用 CSS 时能够正常工作。
RGB 到十六进制
将 RGB 转换为十六进制仅仅是基数的改变。我们使用 toString(16)
将红色、绿色和蓝色值从十进制转换为十六进制。在为单个数字和以下数字添加前导 0
后,我们可以将它们和 #
连接到单个 return
语句中。
function RGBToHex(r,g,b) {
r = r.toString(16);
g = g.toString(16);
b = b.toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
return "#" + r + g + b;
}
字符串中的 RGB 到十六进制
或者,我们可以使用一个带有红色、绿色和蓝色(例如 "rgb(255,25,2)"
、"rgb(255 25 2)"
)以逗号或空格分隔的单个字符串参数。使用子字符串消除 rgb(
,使用 )
分割剩下的部分,然后使用分隔符 (sep
) 分割该结果的第一项。r
、g
和 b
现在将成为局部变量。然后,我们在分割字符串之前使用 +
将它们转换回数字,然后再获取十六进制值。
function RGBToHex(rgb) {
// Choose correct separator
let sep = rgb.indexOf(",") > -1 ? "," : " ";
// Turn "rgb(r,g,b)" into [r,g,b]
rgb = rgb.substr(4).split(")")[0].split(sep);
let r = (+rgb[0]).toString(16),
g = (+rgb[1]).toString(16),
b = (+rgb[2]).toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
return "#" + r + g + b;
}
此外,我们可以通过在重新定义 rgb
后添加循环来允许字符串使用通道值作为百分比。它将去除 %
并将剩下的内容转换为 255 之内的值。
function RGBToHex(rgb) {
let sep = rgb.indexOf(",") > -1 ? "," : " ";
rgb = rgb.substr(4).split(")")[0].split(sep);
// Convert %s to 0–255
for (let R in rgb) {
let r = rgb[R];
if (r.indexOf("%") > -1)
rgb[R] = Math.round(r.substr(0,r.length - 1) / 100 * 255);
/* Example:
75% -> 191
75/100 = 0.75, * 255 = 191.25 -> 191
*/
}
...
}
现在我们可以提供如下值中的任意一个
rgb(255,25,2)
rgb(255 25 2)
rgb(50%,30%,10%)
rgb(50% 30% 10%)
RGBA 到十六进制 (#rrggbbaa)
将 RGBA 转换为带 #rgba 或 #rrggbbaa 表示法的十六进制遵循与不透明对应物几乎相同的过程。由于 alpha (a
) 通常是 0 到 1 之间的值,因此我们需要将其乘以 255,对结果进行四舍五入,然后将其转换为十六进制。
function RGBAToHexA(r,g,b,a) {
r = r.toString(16);
g = g.toString(16);
b = b.toString(16);
a = Math.round(a * 255).toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
if (a.length == 1)
a = "0" + a;
return "#" + r + g + b + a;
}
要使用一个字符串(包括百分比)执行此操作,我们可以按照我们之前所做的操作。还要注意额外的一步,即剪切斜杠。由于 CSS 颜色第 4 级支持 rgba(r g b / a)
的语法,因此我们在此处允许它。Alpha 值现在可以是百分比!这消除了我们过去使用的 0-1-only 限制。因此,循环遍历 rgba
的 for
循环将包括一个部分,用于在不乘以 255 的情况下(当 R
为 alpha 的 3 时)从 alpha 中删除 %
。很快我们就可以使用像 rgba(255 128 0 / 0.8)
和 rgba(100% 21% 100% / 30%)
这样的值!
function RGBAToHexA(rgba) {
let sep = rgba.indexOf(",") > -1 ? "," : " ";
rgba = rgba.substr(5).split(")")[0].split(sep);
// Strip the slash if using space-separated syntax
if (rgba.indexOf("/") > -1)
rgba.splice(3,1);
for (let R in rgba) {
let r = rgba[R];
if (r.indexOf("%") > -1) {
let p = r.substr(0,r.length - 1) / 100;
if (R < 3) {
rgba[R] = Math.round(p * 255);
} else {
rgba[R] = p;
}
}
}
}
然后,在将通道转换为十六进制的地方,我们调整 a
以使用 rgba[]
的一项。
function RGBAToHexA(rgba) {
...
let r = (+rgba[0]).toString(16),
g = (+rgba[1]).toString(16),
b = (+rgba[2]).toString(16),
a = Math.round(+rgba[3] * 255).toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
if (a.length == 1)
a = "0" + a;
return "#" + r + g + b + a;
}
现在该函数支持以下内容
rgba(255,25,2,0.5)
rgba(255 25 2 / 0.5)
rgba(50%,30%,10%,0.5)
rgba(50%,30%,10%,50%)
rgba(50% 30% 10% / 0.5)
rgba(50% 30% 10% / 50%)
十六进制到 RGB
我们知道十六进制值的长度必须是 3 或 6(加上 #
)。在任何一种情况下,我们都以 "0x"
开头每个红色 (r
)、绿色 (g
) 和蓝色 (b
) 值以将其转换为十六进制。如果我们提供一个 3 位数字的值,我们将为每个通道连接相同的值两次。如果是 6 位数字的值,我们将连接前两位用于红色,接下来的两位用于绿色,最后两位用于蓝色。为了获取最终 rgb()
字符串的值,我们在变量前面添加 +
以将其从字符串转换回数字,这将产生我们需要的十进制数。
function hexToRGB(h) {
let r = 0, g = 0, b = 0;
// 3 digits
if (h.length == 4) {
r = "0x" + h[1] + h[1];
g = "0x" + h[2] + h[2];
b = "0x" + h[3] + h[3];
// 6 digits
} else if (h.length == 7) {
r = "0x" + h[1] + h[2];
g = "0x" + h[3] + h[4];
b = "0x" + h[5] + h[6];
}
return "rgb("+ +r + "," + +g + "," + +b + ")";
}
使用 % 从十六进制输出 RGB
如果我们想使用百分比返回 rgb()
,那么我们可以修改函数以使用可选的 isPct
参数,如下所示
function hexToRGB(h,isPct) {
let r = 0, g = 0, b = 0;
isPct = isPct === true;
if (h.length == 4) {
r = "0x" + h[1] + h[1];
g = "0x" + h[2] + h[2];
b = "0x" + h[3] + h[3];
} else if (h.length == 7) {
r = "0x" + h[1] + h[2];
g = "0x" + h[3] + h[4];
b = "0x" + h[5] + h[6];
}
if (isPct) {
r = +(r / 255 * 100).toFixed(1);
g = +(g / 255 * 100).toFixed(1);
b = +(b / 255 * 100).toFixed(1);
}
return "rgb(" + (isPct ? r + "%," + g + "%," + b + "%" : +r + "," + +g + "," + +b) + ")";
}
在最后一个 if
语句下,使用 +
将 r
、g
和 b
转换为数字。每个 toFixed(1)
以及它们将结果四舍五入到最接近的十分之一。此外,我们不会有带有 .0
的整数或产生像 0.30000000000000004
这样的数字的已有几十年的怪癖。因此,在 return
中,我们省略了第一个 r
、g
和 b
之前的 +
以防止由 %
引起的 NaN
。现在我们可以使用 hexToRGB("#ff0",true)
获取 rgb(100%,100%,0%)
!
十六进制 (#rrggbbaa) 到 RGBA
带 alpha 的十六进制值的程序应该与最后一个类似。我们只需检测 4 位或 8 位数字的值(加上 #
),然后转换 alpha 并将其除以 255。为了获得更精确的输出,但不是 alpha 的长十进制数字,我们可以使用 toFixed(3)
。
function hexAToRGBA(h) {
let r = 0, g = 0, b = 0, a = 1;
if (h.length == 5) {
r = "0x" + h[1] + h[1];
g = "0x" + h[2] + h[2];
b = "0x" + h[3] + h[3];
a = "0x" + h[4] + h[4];
} else if (h.length == 9) {
r = "0x" + h[1] + h[2];
g = "0x" + h[3] + h[4];
b = "0x" + h[5] + h[6];
a = "0x" + h[7] + h[8];
}
a = +(a / 255).toFixed(3);
return "rgba(" + +r + "," + +g + "," + +b + "," + a + ")";
}
使用 % 从十六进制输出 RGBA
对于输出百分比的版本,我们可以像在 hexToRGB()
中那样做——当 isPct
为 true
时,将 r
、g
和 b
切换到 0-100%。
function hexAToRGBA(h,isPct) {
let r = 0, g = 0, b = 0, a = 1;
isPct = isPct === true;
// Handling of digits
...
if (isPct) {
r = +(r / 255 * 100).toFixed(1);
g = +(g / 255 * 100).toFixed(1);
b = +(b / 255 * 100).toFixed(1);
}
a = +(a / 255).toFixed(3);
return "rgba(" + (isPct ? r + "%," + g + "%," + b + "%," + a : +r + "," + +g + "," + +b + "," + a) + ")";
}
如果 alpha 也应该是一个百分比,这里有一个快速修复:将 a
被重新定义的语句移到最后一个 if
语句之上。然后在该语句中,修改 a
使其类似于 r
、g
和 b
。当 isPct
为 true 时,a
也必须获得 %
。
function hexAToRGBA(h,isPct) {
...
a = +(a / 255).toFixed(3);
if (isPct) {
r = +(r / 255 * 100).toFixed(1);
g = +(g / 255 * 100).toFixed(1);
b = +(b / 255 * 100).toFixed(1);
a = +(a * 100).toFixed(1);
}
return "rgba(" + (isPct ? r + "%," + g + "%," + b + "%," + a + "%" : +r + "," + +g + "," + +b + "," + a) + ")";
}
当我们现在输入 #7f7fff80
时,我们应该得到 rgba(127,127,255,0.502)
或 rgba(49.8%,49.8%,100%,50.2%)
。
RGB 到 HSL
从 RGB 或十六进制获取 HSL 值有点更具挑战性,因为涉及更大的公式。首先,我们必须将红色、绿色和蓝色除以 255 以使用 0 到 1 之间的值。然后我们找到这些值的最小值和最大值 (cmin
和 cmax
) 以及它们之间的差值 (delta
)。我们需要该结果作为计算色调和饱和度的一部分。在 delta
之后,让我们初始化色调 (h
)、饱和度 (s
) 和亮度 (l
)。
function RGBToHSL(r,g,b) {
// Make r, g, and b fractions of 1
r /= 255;
g /= 255;
b /= 255;
// Find greatest and smallest channel values
let cmin = Math.min(r,g,b),
cmax = Math.max(r,g,b),
delta = cmax - cmin,
h = 0,
s = 0,
l = 0;
}
接下来,我们需要计算色调,它将由 cmax
中的最大通道值确定(或者如果所有通道都相同)。如果通道之间没有差异,则色调将为 0。如果 cmax
是红色,则公式将为 ((g - b) / delta) % 6
。如果是绿色,则为 (b - r) / delta + 2
。然后,如果是蓝色,则为 (r - g) / delta + 4
。最后,将结果乘以 60(以获得度数值)并四舍五入。由于色调不应该为负数,因此如果需要,我们将 360 加到它上面。
function RGBToHSL(r,g,b) {
...
// Calculate hue
// No difference
if (delta == 0)
h = 0;
// Red is max
else if (cmax == r)
h = ((g - b) / delta) % 6;
// Green is max
else if (cmax == g)
h = (b - r) / delta + 2;
// Blue is max
else
h = (r - g) / delta + 4;
h = Math.round(h * 60);
// Make negative hues positive behind 360°
if (h < 0)
h += 360;
}
剩下的只有饱和度和亮度了。在我们计算饱和度之前,先计算亮度,因为饱和度将取决于亮度。它是最大和最小通道值之和的一半 ((cmax + cmin) / 2
)。然后 delta
将决定饱和度是多少。如果它是 0(cmax
和 cmin
之间没有差异),则饱和度自动为 0。否则,它将是 1 减去亮度乘以 2 再减 1 的绝对值 (1 - Math.abs(2 * l - 1)
)。获得这些值后,我们必须将其转换为 100% 的值,因此我们将它们乘以 100 并四舍五入到最接近的十分之一。现在我们可以将我们的 hsl()
串联起来。
function RGBToHSL(r,g,b) {
...
// Calculate lightness
l = (cmax + cmin) / 2;
// Calculate saturation
s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
// Multiply l and s by 100
s = +(s * 100).toFixed(1);
l = +(l * 100).toFixed(1);
return "hsl(" + h + "," + s + "%," + l + "%)";
}
字符串中的 RGB 到 HSL
对于一个字符串,用逗号或空格分割参数,去除 %
,并像之前一样本地化 r
、g
和 b
。
function RGBToHSL(rgb) {
let sep = rgb.indexOf(",") > -1 ? "," : " ";
rgb = rgb.substr(4).split(")")[0].split(sep);
for (let R in rgb) {
let r = rgb[R];
if (r.indexOf("%") > -1)
rgb[R] = Math.round(r.substr(0,r.length - 1) / 100 * 255);
}
// Make r, g, and b fractions of 1
let r = rgb[0] / 255,
g = rgb[1] / 255,
b = rgb[2] / 255;
...
}
RGBA 到 HSLA
与我们刚刚将 RGB 转换为 HSL 所做的相比,alpha 的对应部分基本上什么都不需要做!我们只需重用 RGB 到 HSL 的代码(多参数版本),保持 a
不变,并将 a
传递到返回的 HSLA 中。请记住,它应该在 0 和 1 之间。
function RGBAToHSLA(r,g,b,a) {
// Code for RGBToHSL(r,g,b) before return
...
return "hsla(" + h + "," + s + "%," +l + "%," + a + ")";
}
字符串中的 RGBA 到 HSLA
对于字符串值,我们再次应用分割和去除逻辑,但使用 rgba
中的第四个项目作为 a
。还记得新的 rgba(r g b / a)
语法吗?我们像 RGBAToHexA()
一样采用了它的接受。然后其余代码是正常的 RGB 到 HSL 转换。
function RGBAToHSLA(rgba) {
let sep = rgba.indexOf(",") > -1 ? "," : " ";
rgba = rgba.substr(5).split(")")[0].split(sep);
// Strip the slash if using space-separated syntax
if (rgba.indexOf("/") > -1)
rgba.splice(3,1);
for (let R in rgba) {
let r = rgba[R];
if (r.indexOf("%") > -1) {
let p = r.substr(0,r.length - 1) / 100;
if (R < 3) {
rgba[R] = Math.round(p * 255);
} else {
rgba[R] = p;
}
}
}
// Make r, g, and b fractions of 1
let r = rgba[0] / 255,
g = rgba[1] / 255,
b = rgba[2] / 255,
a = rgba[3];
// Rest of RGB-to-HSL logic
...
}
希望将 alpha 保持原样?从 for
循环中删除 else
语句。
for (let R in rgba) {
let r = rgba[R];
if (r.indexOf("%") > -1) {
let p = r.substr(0,r.length - 1) / 100;
if (R < 3) {
rgba[R] = Math.round(p * 255);
}
}
}
HSL 到 RGB
将 HSL 转换回 RGB 比反向转换需要更少的逻辑。由于我们将使用 0-100 的范围表示饱和度和亮度,因此第一步是将它们除以 100 以获得 0 到 1 之间的值。接下来,我们找到色度 (c
),它是颜色强度,因此它是 (1 - Math.abs(2 * l - 1)) * s
。然后我们使用 x
表示第二大的分量(第一个是色度),每个通道需要添加的量以匹配亮度 (m
),并初始化 r
、g
、b
。
function HSLToRGB(h,s,l) {
// Must be fractions of 1
s /= 100;
l /= 100;
let c = (1 - Math.abs(2 * l - 1)) * s,
x = c * (1 - Math.abs((h / 60) % 2 - 1)),
m = l - c/2,
r = 0,
g = 0,
b = 0;
}
色调将决定红色、绿色和蓝色应该是什么,具体取决于它位于色轮的哪个 60° 扇区。

然后 c
和 x
将按如下所示分配,使一个通道为 0。要获得最终的 RGB 值,我们将 m
添加到每个通道,乘以 255,并四舍五入。
function HSLToRGB(h,s,l) {
...
if (0 <= h && h < 60) {
r = c; g = x; b = 0;
} else if (60 <= h && h < 120) {
r = x; g = c; b = 0;
} else if (120 <= h && h < 180) {
r = 0; g = c; b = x;
} else if (180 <= h && h < 240) {
r = 0; g = x; b = c;
} else if (240 <= h && h < 300) {
r = x; g = 0; b = c;
} else if (300 <= h && h < 360) {
r = c; g = 0; b = x;
}
r = Math.round((r + m) * 255);
g = Math.round((g + m) * 255);
b = Math.round((b + m) * 255);
return "rgb(" + r + "," + g + "," + b + ")";
}
字符串中的 HSL 到 RGB
对于单个字符串版本,我们修改前几个语句的方式与 RGBToHSL(r,g,b)
基本相同。删除 s /= 100;
和 l /= 100;
,我们将使用新的语句擦除 HSL 值数组的前 4 个字符和 )
,然后在将 s
和 l
除以 100 之前,擦除 s
和 l
中的 %
。
function HSLToRGB(hsl) {
let sep = hsl.indexOf(",") > -1 ? "," : " ";
hsl = hsl.substr(4).split(")")[0].split(sep);
let h = hsl[0],
s = hsl[1].substr(0,hsl[1].length - 1) / 100,
l = hsl[2].substr(0,hsl[2].length - 1) / 100;
...
}
接下来的几个语句将处理带有单位(度、弧度或圈数)提供的色调。我们将弧度乘以 180/π,并将圈数乘以 360。如果结果超过 360,我们将进行复合模数除法以将其保持在范围内。所有这些都将在我们处理 c
、x
和 m
之前发生。
function HSLToRGB(hsl) {
...
// Strip label and convert to degrees (if necessary)
if (h.indexOf("deg") > -1)
h = h.substr(0,h.length - 3);
else if (h.indexOf("rad") > -1)
h = Math.round(h.substr(0,h.length - 3) * (180 / Math.PI));
else if (h.indexOf("turn") > -1)
h = Math.round(h.substr(0,h.length - 4) * 360);
// Keep hue fraction of 360 if ending up over
if (h >= 360)
h %= 360;
// Conversion to RGB begins
...
}
在实施上述步骤后,现在可以安全地使用以下内容
hsl(180 100% 50%)
hsl(180deg,100%,50%)
hsl(180deg 100% 50%)
hsl(3.14rad,100%,50%)
hsl(3.14rad 100% 50%)
hsl(0.5turn,100%,50%)
hsl(0.5turn 100% 50%)
哇,这真是太灵活了!
使用 % 从 HSL 输出 RGB
类似地,我们可以修改此函数以返回百分比值,就像我们在 hexToRGB()
中所做的那样。
function HSLToRGB(hsl,isPct) {
let sep = hsl.indexOf(",") > -1 ? "," : " ";
hsl = hsl.substr(4).split(")")[0].split(sep);
isPct = isPct === true;
...
if (isPct) {
r = +(r / 255 * 100).toFixed(1);
g = +(g / 255 * 100).toFixed(1);
b = +(b / 255 * 100).toFixed(1);
}
return "rgb("+ (isPct ? r + "%," + g + "%," + b + "%" : +r + "," + +g + "," + +b) + ")";
}
HSLA 到 RGBA
再次,处理 alpha 将非常简单。我们可以重新应用原始 HSLToRGB(h,s,l)
的代码并将 a
添加到 return
中。
function HSLAToRGBA(h,s,l,a) {
// Code for HSLToRGB(h,s,l) before return
...
return "rgba(" + r + "," + g + "," + b + "," + a + ")";
}
字符串中的 HSLA 到 RGBA
将其更改为一个参数,我们在这里处理字符串的方式与之前没有什么不同。颜色级别 4 中的新 HSLA 语法使用 (value value value / value)
,就像 RGBA 一样,因此拥有处理它的代码,我们将能够在这里插入类似 hsla(210 100% 50% / 0.5)
的内容。
function HSLAToRGBA(hsla) {
let sep = hsla.indexOf(",") > -1 ? "," : " ";
hsla = hsla.substr(5).split(")")[0].split(sep);
if (hsla.indexOf("/") > -1)
hsla.splice(3,1);
let h = hsla[0],
s = hsla[1].substr(0,hsla[1].length - 1) / 100,
l = hsla[2].substr(0,hsla[2].length - 1) / 100,
a = hsla[3];
if (h.indexOf("deg") > -1)
h = h.substr(0,h.length - 3);
else if (h.indexOf("rad") > -1)
h = Math.round(h.substr(0,h.length - 3) * (180 / Math.PI));
else if (h.indexOf("turn") > -1)
h = Math.round(h.substr(0,h.length - 4) * 360);
if (h >= 360)
h %= 360;
...
}
此外,以下其他组合也成为可能
hsla(180,100%,50%,50%)
hsla(180 100% 50% / 50%)
hsla(180deg,100%,50%,0.5)
hsla(3.14rad,100%,50%,0.5)
hsla(0.5turn 100% 50% / 50%)
使用 % 从 HSLA 输出 RGBA
然后我们可以复制相同的逻辑来输出百分比,包括 alpha。如果 alpha 应该是一个百分比(在 pctFound
中搜索),以下是如何处理它
- 如果
r
、g
和b
要转换为百分比,则a
应乘以 100,如果它还不是百分比。否则,删除 %,它将在return
中添加回来。 - 如果
r
、g
和b
应该保持不变,则从a
中删除 % 并将a
除以 100。
function HSLAToRGBA(hsla,isPct) {
// Code up to slash stripping
...
isPct = isPct === true;
// h, s, l, a defined to rounding of r, g, b
...
let pctFound = a.indexOf("%") > -1;
if (isPct) {
r = +(r / 255 * 100).toFixed(1);
g = +(g / 255 * 100).toFixed(1);
b = +(b / 255 * 100).toFixed(1);
if (!pctFound) {
a *= 100;
} else {
a = a.substr(0,a.length - 1);
}
} else if (pctFound) {
a = a.substr(0,a.length - 1) / 100;
}
return "rgba("+ (isPct ? r + "%," + g + "%," + b + "%," + a + "%" : +r + ","+ +g + "," + +b + "," + +a) + ")";
}
十六进制到 HSL
您可能会认为这个和下一个过程比其他的更复杂,但它们只是由两部分组成,并使用了循环逻辑。首先,我们将十六进制转换为 RGB。这给了我们转换为 HSL 所需的十进制数。
function hexToHSL(H) {
// Convert hex to RGB first
let r = 0, g = 0, b = 0;
if (H.length == 4) {
r = "0x" + H[1] + H[1];
g = "0x" + H[2] + H[2];
b = "0x" + H[3] + H[3];
} else if (H.length == 7) {
r = "0x" + H[1] + H[2];
g = "0x" + H[3] + H[4];
b = "0x" + H[5] + H[6];
}
// Then to HSL
r /= 255;
g /= 255;
b /= 255;
let cmin = Math.min(r,g,b),
cmax = Math.max(r,g,b),
delta = cmax - cmin,
h = 0,
s = 0,
l = 0;
if (delta == 0)
h = 0;
else if (cmax == r)
h = ((g - b) / delta) % 6;
else if (cmax == g)
h = (b - r) / delta + 2;
else
h = (r - g) / delta + 4;
h = Math.round(h * 60);
if (h < 0)
h += 360;
l = (cmax + cmin) / 2;
s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
s = +(s * 100).toFixed(1);
l = +(l * 100).toFixed(1);
return "hsl(" + h + "," + s + "%," + l + "%)";
}
十六进制 (#rrggbbaa) 到 HSLA
这个函数中变化的行不多。我们将重复我们最近为获取 alpha 而执行的操作,通过转换十六进制,但不会立即将其除以 255。首先,我们必须像在其他 to-HSL 函数中那样获取色调、饱和度和亮度。然后,在结束的 return
之前,我们除以 alpha 并设置小数位数。
function hexAToHSLA(H) {
let r = 0, g = 0, b = 0, a = 1;
if (H.length == 5) {
r = "0x" + H[1] + H[1];
g = "0x" + H[2] + H[2];
b = "0x" + H[3] + H[3];
a = "0x" + H[4] + H[4];
} else if (H.length == 9) {
r = "0x" + H[1] + H[2];
g = "0x" + H[3] + H[4];
b = "0x" + H[5] + H[6];
a = "0x" + H[7] + H[8];
}
// Normal conversion to HSL
...
a = (a / 255).toFixed(3);
return "hsla("+ h + "," + s + "%," + l + "%," + a + ")";
}
HSL 转十六进制
这个转换首先是转换为 RGB,但在将 RGB 结果转换为十六进制的 Math.round()
中多了一个步骤。
function HSLToHex(h,s,l) {
s /= 100;
l /= 100;
let c = (1 - Math.abs(2 * l - 1)) * s,
x = c * (1 - Math.abs((h / 60) % 2 - 1)),
m = l - c/2,
r = 0,
g = 0,
b = 0;
if (0 <= h && h < 60) {
r = c; g = x; b = 0;
} else if (60 <= h && h < 120) {
r = x; g = c; b = 0;
} else if (120 <= h && h < 180) {
r = 0; g = c; b = x;
} else if (180 <= h && h < 240) {
r = 0; g = x; b = c;
} else if (240 <= h && h < 300) {
r = x; g = 0; b = c;
} else if (300 <= h && h < 360) {
r = c; g = 0; b = x;
}
// Having obtained RGB, convert channels to hex
r = Math.round((r + m) * 255).toString(16);
g = Math.round((g + m) * 255).toString(16);
b = Math.round((b + m) * 255).toString(16);
// Prepend 0s, if necessary
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
return "#" + r + g + b;
}
字符串形式的 HSL 转十六进制
如果我们将它修改为接受单个字符串,那么此函数的前几行将类似于 HSLToRGB()
中的那些行。这就是我们最初分别获取色相、饱和度和亮度的方式。我们也不要忘记删除色相标签并转换为角度的步骤。所有这些都将替换 s /= 100;
和 l /= 100;
。
function HSLToHex(hsl) {
let sep = hsl.indexOf(",") > -1 ? "," : " ";
hsl = hsl.substr(4).split(")")[0].split(sep);
let h = hsl[0],
s = hsl[1].substr(0,hsl[1].length - 1) / 100,
l = hsl[2].substr(0,hsl[2].length - 1) / 100;
// Strip label and convert to degrees (if necessary)
if (h.indexOf("deg") > -1)
h = h.substr(0,h.length - 3);
else if (h.indexOf("rad") > -1)
h = Math.round(h.substr(0,h.length - 3) * (180 / Math.PI));
else if (h.indexOf("turn") > -1)
h = Math.round(h.substr(0,h.length - 4) * 360);
if (h >= 360)
h %= 360;
...
}
HSLA 转十六进制(#rrggbbaa)
添加 alpha 后,我们将 a
转换为十六进制并添加第四个 if
,如果需要,则在前面添加一个 0。您可能已经熟悉此逻辑,因为我们上次在 RGBAToHexA()
中使用了它。
function HSLAToHexA(h,s,l,a) {
// Repeat code from HSLToHex(h,s,l) until 3 `toString(16)`s
...
a = Math.round(a * 255).toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
if (a.length == 1)
a = "0" + a;
return "#" + r + g + b + a;
}
字符串形式的 HSLA 转十六进制(#rrggbbaa)
最后,单参数版本的行到 a = hsla[3]
与 HSLAToRGBA()
的行没有区别。
function HSLAToHexA(hsla) {
let sep = hsla.indexOf(",") > -1 ? "," : " ";
hsla = hsla.substr(5).split(")")[0].split(sep);
// Strip the slash
if (hsla.indexOf("/") > -1)
hsla.splice(3,1);
let h = hsla[0],
s = hsla[1].substr(0,hsla[1].length - 1) / 100,
l = hsla[2].substr(0,hsla[2].length - 1) / 100,
a = hsla[3];
...
}
内置名称
要将命名颜色转换为 RGB、十六进制或 HSL,您可以考虑将此包含 140 多个名称和十六进制值的表格转换为一个大型对象,作为开始。事实是,我们真的不需要一个,因为我们可以这样做
- 创建一个元素
- 为其指定文本颜色
- 获取该属性的值
- 删除元素
- 返回存储的颜色值,默认情况下该值将为 RGB
因此,我们获取 RGB 的函数将只有七个语句!
function nameToRGB(name) {
// Create fake div
let fakeDiv = document.createElement("div");
fakeDiv.style.color = name;
document.body.appendChild(fakeDiv);
// Get color of div
let cs = window.getComputedStyle(fakeDiv),
pv = cs.getPropertyValue("color");
// Remove div after obtaining desired color value
document.body.removeChild(fakeDiv);
return pv;
}
让我们更进一步。我们如何将输出更改为十六进制?
function nameToHex(name) {
// Get RGB from named color in temporary div
let fakeDiv = document.createElement("div");
fakeDiv.style.color = name;
document.body.appendChild(fakeDiv);
let cs = window.getComputedStyle(fakeDiv),
pv = cs.getPropertyValue("color");
document.body.removeChild(fakeDiv);
// Code ripped from RGBToHex() (except pv is substringed)
let rgb = pv.substr(4).split(")")[0].split(","),
r = (+rgb[0]).toString(16),
g = (+rgb[1]).toString(16),
b = (+rgb[2]).toString(16);
if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;
return "#" + r + g + b;
}
或者,为什么不使用 HSL 呢?😉
function nameToHSL(name) {
let fakeDiv = document.createElement("div");
fakeDiv.style.color = name;
document.body.appendChild(fakeDiv);
let cs = window.getComputedStyle(fakeDiv),
pv = cs.getPropertyValue("color");
document.body.removeChild(fakeDiv);
// Code ripped from RGBToHSL() (except pv is substringed)
let rgb = pv.substr(4).split(")")[0].split(","),
r = rgb[0] / 255,
g = rgb[1] / 255,
b = rgb[2] / 255,
cmin = Math.min(r,g,b),
cmax = Math.max(r,g,b),
delta = cmax - cmin,
h = 0,
s = 0,
l = 0;
if (delta == 0)
h = 0;
else if (cmax == r)
h = ((g - b) / delta) % 6;
else if (cmax == g)
h = (b - r) / delta + 2;
else
h = (r - g) / delta + 4;
h = Math.round(h * 60);
if (h < 0)
h += 360;
l = (cmax + cmin) / 2;
s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
s = +(s * 100).toFixed(1);
l = +(l * 100).toFixed(1);
return "hsl(" + h + "," + s + "%," + l + "%)";
}
从长远来看,从名称到 RGB 的每次转换,在破解名称后都会变成从 RGB 的转换。
验证颜色
在所有这些函数中,都没有任何措施来防止或纠正荒谬的输入(例如超过 360 的色相或超过 100 的百分比)。如果我们只是操作使用 getImageData()
获取的 上的像素,则在转换之前不需要验证颜色值,因为无论如何它们都是正确的。如果我们正在创建用户提供颜色的颜色转换工具,那么验证将非常必要。
像这样处理 RGB 的单独参数作为通道的错误输入很容易
// Correct red
if (r > 255)
r = 255;
else if (r < 0)
r = 0;
如果要验证整个字符串,则需要正则表达式。例如,这是给定验证步骤和表达式的 RGBToHex()
函数
function RGBToHex(rgb) {
// Expression for rgb() syntaxes
let ex = /^rgb\((((((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?)){2}|((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s)){2})((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]))|((((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){2}|((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){2})(([1-9]?\d(\.\d+)?)|100|(\.\d+))%))\)$/i;
if (ex.test(rgb)) {
// Logic to convert RGB to hex
...
} else {
// Something to do if color is invalid
}
}
要测试其他类型的值,下表列出了涵盖不透明和启用 alpha 的表达式
颜色值 | 正则表达式 |
---|---|
RGB | /^rgb\((((((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?)){2}|((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s)){2})((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]))|((((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){2}|((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){2})(([1-9]?\d(\.\d+)?)|100|(\.\d+))%))\)$/i |
RGBA | /^rgba\((((((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?)){3})|(((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){3}))|(((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s){3})|(((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){3}))\/\s)((0?\.\d+)|[01]|(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)\)$/i |
十六进制 | /^#([\da-f]{3}){1,2}$/i |
十六进制(带 Alpha) | /^#([\da-f]{4}){1,2}$/i |
HSL | /^hsl\(((((([12]?[1-9]?\d)|[12]0\d|(3[0-5]\d))(\.\d+)?)|(\.\d+))(deg)?|(0|0?\.\d+)turn|(([0-6](\.\d+)?)|(\.\d+))rad)((,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}|(\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2})\)$/i |
HSLA | /^hsla\(((((([12]?[1-9]?\d)|[12]0\d|(3[0-5]\d))(\.\d+)?)|(\.\d+))(deg)?|(0|0?\.\d+)turn|(([0-6](\.\d+)?)|(\.\d+))rad)(((,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2},\s?)|((\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}\s\/\s))((0?\.\d+)|[01]|(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)\)$/i |
查看 RGB(A) 和 HSL(A) 的表达式,您现在可能睁大了眼睛;这些表达式已经变得足够全面,可以包含 CSS Colors Level 4 中的大多数新语法。另一方面,十六进制不需要像其他表达式那样长,因为它只包含数字计数。稍后,我们将分析这些表达式并解读各个部分。请注意,不区分大小写的值 (/i
) 通过所有这些验证。
RGB
/^rgb\((((((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?)){2}|((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s)){2})((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]))|((((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){2}|((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){2})(([1-9]?\d(\.\d+)?)|100|(\.\d+))%))\)$/i
因为 rgb()
接受所有整数或所有百分比,所以两种情况都涵盖了。在最外层组中,在 ^rgb\(
和 \)$
之间,有两个内部组分别用于整数和百分比,所有组都以逗号空格或仅空格作为分隔符
(((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?){2}|(((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s){2})((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]))
((((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){2}|((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){2})(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)
在前半部分,我们接受红色和绿色的两个整数实例,范围从 0 到 99 或 111 到 199 ((1?[1-9]?\d)
)、100 到 109 (10\d
)、200 到 249 ((2[0-4]\d)
) 或 250 到 255 (25[0-5]
)。我们不能简单地使用 \d{1,3}
,因为像 03 或 017 以及大于 255 的值都不应该被允许。之后是逗号和可选空格 (,\s?)。在 |
的另一侧,在第一个 {2}
(表示两个整数实例)之后,如果左侧为假,我们将检查相同内容,但使用空格分隔符。然后对于蓝色,应该接受相同的内容,但没有分隔符。
在另一半中,应该接受包括浮点数在内的百分比的可接受值,这些值可以是 0 到 99,明确地是 100 而不是浮点数,或者小于 1 且省略了 0 的浮点数。因此,此处的片段为 (([1-9]?\d(\.\d+)?)|100|(\.\d+))
,它出现了三次;两次带分隔符 (,\s?){2}
、%\s){2}
),一次不带分隔符。
在 CSS 中,使用不带空格分隔符的百分比 (例如 rgb(100%50%10%)
) 是合法的,但我们编写的函数不支持该功能。对于 rgba(100%50%10%/50%)
、hsl(40 100%50%)
和 hsla(40 100%50%/0.5)
也是如此。这对于代码高尔夫和压缩来说可能是一个很大的优势!
RGBA
/^rgba\((((((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?)){3})|(((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){3}))|(((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5])\s){3})|(((([1-9]?\d(\.\d+)?)|100|(\.\d+))%\s){3}))\/\s)((0?\.\d+)|[01]|(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)\)$/i
下一个表达式与上一个非常相似,但检查了三个整数实例 (((((1?[1-9]?\d)|10\d|(2[0-4]\d)|25[0-5]),\s?){3})
) 或百分比 ((((([1-9]?\d(\.\d+)?)|100|(\.\d+))%,\s?){3})
),以及逗号可选空格。否则,它将查找相同的内容,但使用空格分隔符,以及蓝色的后斜杠和空格 (\/\s
)。接下来是 ((0?\.\d+)|[01]|(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)
,我们接受带或不带第一个 0 的浮点数 ((0?\.\d+)
)、点上的 0 或 1 ([01]
) 或 0 到 100% ((([1-9]?\d(\.\d+)?)|100|(\.\d+))%
)。
带 Alpha 的十六进制
// #rgb/#rrggbb
/^#([\da-f]{3}){1,2}$/i
// #rgba/#rrggbbaa
/^#([\da-f]{4}){1,2}$/i
对于带和不带 alpha 的十六进制,都接受数字或字母 a 到 f 的实例 ([\da-f]
)。然后计算此实例的一个或两个实例,以获取提供的简写或长格式值(#rgb 或 #rrggbb)。举例来说,我们有这个相同的简写模式:/^#([\da-f]{n}){1,2}$/i
。只需将 n 更改为 3 或 4 即可。
HSL 和 HSLA
// HSL
/^hsl\((((((\[12]?[1-9]?\d)|[12]0\d|(3[0-5]\d))(\.\d+)?)|(\.\d+))(deg)?|(0|0?\.\d+)turn|(([0-6\\.\d+)?)|(\.\d+))rad)((,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}|(\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2})\)$/i
// HSLA
/^hsla\((((((\[12]?[1-9]?\d)|[12]0\d|(3[0-5]\d))(\.\d+)?)|(\.\d+))(deg)?|(0|0?\.\d+)turn|(([0-6\\.\d+)?)|(\.\d+))rad)(((,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2},\s?)|((\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}\s\/\s))((0?\.\d+)|[01]|(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)\)$/i
在 HSL 和 HSLA 的两个表达式中的 \(
之后,这大块代码用于色相
(((((\[12]?[1-9]?\d)|[12]0\d|(3[0-5]\d))(\.\d+)?)|(\.\d+))(deg)?|(0|0?\.\d+)turn|(([0-6\\.\d+)?)|(\.\d+))rad)
([12]?[1-9]?\d)
涵盖 0 到 99、110 到 199 和 210 到 299。[12]0\d
涵盖 110 到 109 和 200 到 209。然后 (3[0-5]\d)
处理 300 到 359。这样划分范围的原因与 rgb()
语法中的整数类似:排除第一个为零的值和大于最大值的值。由于色相可以是浮点数,因此第一个 (\.\d+)?
用于表示浮点数。
在上述代码段之后的 |
旁边,第二个 (\.\d+)
用于表示没有前导零的浮点数。
现在让我们向上提升一级并解读下一个小块
(deg)?|(0|0?\.\d+)turn|((\[0-6\\.\d+)?)|(\.\d+))rad
这段代码包含了我们可以用于色相的标签——度数、圈数或弧度。我们可以包含所有或不包含deg
。turn
中的值必须小于1。对于弧度,我们可以接受0到7之间的任何浮点数。但是我们知道,一个360°的圆周是2π,它大约在6.28处停止。你可能会认为6.3及以上不应该被接受。因为2π是一个无理数,在这个例子中尝试满足JavaScript控制台提供的每个小数位会太麻烦了。此外,如果色相为360°或更大,我们在HSLTo_()
函数中有一个作为第二层安全措施的代码片段。
// Keep hue fraction of 360 if ending up over
if (h >= 360)
h %= 360;
现在让我们向上提升一级,解读第二部分。
(,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}
我们正在统计饱和度和亮度两个逗号-空格-百分比的实例(空格可选)。在,\s?
后面的组中,我们测试0到99之间的值,是否带有小数点(([1-9]?\d(\.\d+)?)
),正好是100,或者小于1的浮点数,不带前导0((\.\d+)
)。
HSL表达式的最后一部分,在结束符(\)$/i
)之前,是一个类似的表达式,如果空格是唯一的分隔符。
(\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}
\s
在开头而不是,\s?
。然后在HSLA表达式中,这个相同的代码块位于另一个组中,其后跟着,\s?
的{2}
。
((,\s?(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2},\s?)
这计算了亮度和alpha之间的逗号-空格。然后,如果我们使用空格作为分隔符,我们需要在统计两个空格和一个百分比后检查空格-斜杠-空格(\s\/\s
)。
((\s(([1-9]?\d(\.\d+)?)|100|(\.\d+))%){2}\s\/\s))
之后,我们剩下要检查alpha值。
(((0?\.\d+)|[01])|(([1-9]?\d(\.\d+)?)|100|(\.\d+))%)
(0?\.\d+)
的匹配项包括小于1的浮点数,是否带前导0,[01]
表示0或1,以及0到100%。
结论
如果你的当前挑战是将一个颜色空间转换为另一个颜色空间,你现在有一些关于如何处理它的想法。因为在一个帖子中遍历所有发明过的颜色空间会很乏味,所以我们讨论了最实用和浏览器支持的颜色空间。如果你想超越支持的颜色空间(比如CMYK、XYZ或CIE Lab*),EasyRGB提供了一套很棒的代码就绪公式。
为了查看此处演示的所有转换,我设置了一个CodePen 演示,它在一个表格中显示输入和输出。你可以在第2到10行尝试不同的颜色,并在JavaScript面板中查看完整的函数。
我写了一个基于canvas的“任何CSS颜色到十六进制”的小工具;它在Stack Overflow上:https://stackoverflow.com/questions/1573053/javascript-function-to-convert-color-names-to-hex-codes/24390910#24390910
有人回复了一个非常简洁的
function standardize_color(str) { var c = document.createElement(‘canvas’).getContext(‘2d’); c.fillStyle = str; return c.fillStyle; }
嗨!我上周写了一个类似的工具!目前它只在十六进制和RGB之间转换。请查看:https://boxdox.github.io/hex2rgb/
不错!
我敢打赌这将成为关于该主题的持久经典资源。
投入了令人印象深刻的工作量来分享这一点。谢谢!一堆。我碰巧
一直在开发一个颜色选择器网络应用程序……有了这篇文章,我现在没有任何借口
在不包含你提到的至少一些转换的情况下启动其MVP,以使其
变得更好。
在页面上将会有一个署名。干杯!
我喜欢这篇文章展示了从十进制到十六进制再转换回十进制的数学方法,但我想指出在Javascript中有一种更简单的方法来进行十六进制到十进制的转换:使用带
base
参数的parseInt
。例如,parseInt('FF',16)
将返回整数255
。这几乎就是全部内容。这只是一个我在编写颜色混合器时学到的小技巧,很多年以前。我确实需要重新编写混合器,使其更灵活和全面。你关于RGB到HSL和反向转换的部分将对此有很大帮助,所以非常感谢!
非常有趣的文章。我最近发布了一个关于颜色的网站
https://hexcol.com/
很遗憾我没有早点看到这篇文章。