处理颜色的方法
JeremyJone ... 2024-6-18 大约 5 分钟
# 处理颜色的方法
相信对于前端来说,颜色的处理一定是越来越多的。
下面这些属性可以任意变换,使用时需要注意引用关系。
# 颜色的转换
- rgb:一个颜色的 RGB 表示,{r:255, g:255, b:255[, a:100]}
- hex:一个颜色的 HEX 表示,#123456[FF]
- hsv:一个颜色的 HSV 表示,{h:360, s:100, v:100[, a:100]}
# rgb -> hex
export function rgbToHex({ r, g, b, a }) {
const alpha = a !== void 0;
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
if (r > 255 || g > 255 || b > 255 || (alpha && a > 100)) {
throw new TypeError(
"Expected 3 numbers below 256 (and optionally one below 100)"
);
}
a = alpha
? (Math.round((255 * a) / 100) | (1 << 8)).toString(16).slice(1)
: "";
return "#" + (b | (g << 8) | (r << 16) | (1 << 24)).toString(16).slice(1) + a;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# hex -> rgb
export function hexToRgb(hex) {
if (typeof hex !== "string") {
throw new TypeError("Expected a string");
}
hex = hex.replace(/^#/, "");
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
} else if (hex.length === 4) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
}
const num = parseInt(hex, 16);
return hex.length > 6
? {
r: (num >> 24) & 255,
g: (num >> 16) & 255,
b: (num >> 8) & 255,
a: Math.round((num & 255) / 2.55)
}
: { r: num >> 16, g: (num >> 8) & 255, b: num & 255 };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# hsv -> rgb
export function hsvToRgb({ h, s, v, a }) {
let r, g, b;
s = s / 100;
v = v / 100;
h = h / 360;
const i = Math.floor(h * 6),
f = h * 6 - i,
p = v * (1 - s),
q = v * (1 - f * s),
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
a
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# rgb -> hsv
export function rgbToHsv({ r, g, b, a }) {
const max = Math.max(r, g, b),
min = Math.min(r, g, b),
d = max - min,
s = max === 0 ? 0 : d / max,
v = max / 255;
let h;
switch (max) {
case min:
h = 0;
break;
case r:
h = g - b + d * (g < b ? 6 : 0);
h /= 6 * d;
break;
case g:
h = b - r + d * 2;
h /= 6 * d;
break;
case b:
h = r - g + d * 4;
h /= 6 * d;
break;
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
v: Math.round(v * 100),
a
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# text -> rgb
const reRGBA = /^rgb(a)?\((\d{1,3}),(\d{1,3}),(\d{1,3}),?([01]?\.?\d*?)?\)$/;
export function textToRgb(str) {
if (typeof str !== "string") {
throw new TypeError("Expected a string");
}
const color = str.replace(/ /g, "");
const m = reRGBA.exec(color);
if (m === null) {
return hexToRgb(color);
}
const rgb = {
r: Math.min(255, parseInt(m[2], 10)),
g: Math.min(255, parseInt(m[3], 10)),
b: Math.min(255, parseInt(m[4], 10))
};
if (m[1]) {
const alpha = parseFloat(m[5]);
rgb.a = Math.min(1, isNaN(alpha) === true ? 1 : alpha) * 100;
}
return rgb;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 颜色的处理
# 变亮/变暗
percent 为正值变亮,负值变暗
export function lighten(color, percent) {
if (typeof color !== "string") {
throw new TypeError("Expected a string as color");
}
if (typeof percent !== "number") {
throw new TypeError("Expected a numeric percent");
}
const rgb = textToRgb(color),
t = percent < 0 ? 0 : 255,
p = Math.abs(percent) / 100,
R = rgb.r,
G = rgb.g,
B = rgb.b;
return (
"#" +
(
0x1000000 +
(Math.round((t - R) * p) + R) * 0x10000 +
(Math.round((t - G) * p) + G) * 0x100 +
(Math.round((t - B) * p) + B)
)
.toString(16)
.slice(1)
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 计算颜色的相对亮度
给出一个 color,返回 0-1
export function luminosity(color) {
if (typeof color !== "string" && (!color || color.r === void 0)) {
throw new TypeError("Expected a string or a {r, g, b} object as color");
}
const rgb = typeof color === "string" ? textToRgb(color) : color,
r = rgb.r / 255,
g = rgb.g / 255,
b = rgb.b / 255,
R = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4),
G = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4),
B = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 计算颜色的亮度
给出一个 color,返回 0-255。小于 128 应视为暗色。
export function brightness(color) {
if (typeof color !== "string" && (!color || color.r === void 0)) {
throw new TypeError("Expected a string or a {r, g, b} object as color");
}
const rgb = typeof color === "string" ? textToRgb(color) : color;
return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 混合两种颜色
接收两个颜色,第一个为前景色,第二个为背景色。
- 前景色需要具有透明属性,才会混合背景色,否则返回的仍然是前景色。
- 如果背景色具有透明属性,那么返回的属性也会具有一定的透明性。
export function blend(fgColor, bgColor) {
if (typeof fgColor !== "string" && (!fgColor || fgColor.r === void 0)) {
throw new TypeError(
"Expected a string or a {r, g, b[, a]} object as fgColor"
);
}
if (typeof bgColor !== "string" && (!bgColor || bgColor.r === void 0)) {
throw new TypeError(
"Expected a string or a {r, g, b[, a]} object as bgColor"
);
}
const rgb1 = typeof fgColor === "string" ? textToRgb(fgColor) : fgColor,
r1 = rgb1.r / 255,
g1 = rgb1.g / 255,
b1 = rgb1.b / 255,
a1 = rgb1.a !== void 0 ? rgb1.a / 100 : 1,
rgb2 = typeof bgColor === "string" ? textToRgb(bgColor) : bgColor,
r2 = rgb2.r / 255,
g2 = rgb2.g / 255,
b2 = rgb2.b / 255,
a2 = rgb2.a !== void 0 ? rgb2.a / 100 : 1,
a = a1 + a2 * (1 - a1),
r = Math.round(((r1 * a1 + r2 * a2 * (1 - a1)) / a) * 255),
g = Math.round(((g1 * a1 + g2 * a2 * (1 - a1)) / a) * 255),
b = Math.round(((b1 * a1 + b2 * a2 * (1 - a1)) / a) * 255);
const ret = { r, g, b, a: Math.round(a * 100) };
return typeof fgColor === "string" ? rgbToHex(ret) : ret;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 修改颜色的透明属性
给出一个 color,然后给定其透明度的偏移量。
- offset 需要接收 (-1, 1) 之间的浮点数。 -0.1 表示减少10%,0.1 表示增加10%。
export function changeAlpha(color, offset) {
if (typeof color !== "string") {
throw new TypeError("Expected a string as color");
}
if (offset === void 0 || offset < -1 || offset > 1) {
throw new TypeError("Expected offset to be between -1 and 1");
}
const { r, g, b, a } = textToRgb(color);
const alpha = a !== void 0 ? a / 100 : 0;
return rgbToHex({
r,
g,
b,
a: Math.round(Math.min(1, Math.max(0, alpha + offset)) * 100)
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 合并透明度
有时候我们需要将一个带透明度的颜色,转换为一个不带透明度的颜色,同时要保持颜色看上去没有变化,就不能单单使用合并两个颜色的方法或者修改透明度的方法。
此时需要一个背景颜色。因为带透明度的颜色看上去的效果一定是和背景色有关,默认白色。
export function rgbaToRgb(color, bg = "#fff") {
if (typeof color !== "string" || typeof bg !== "string") {
throw new TypeError("Expected a string as color");
}
const { r, g, b, a } = textToRgb(color);
if (!a) return color;
const bgColor = textToRgb(bg);
return rgbToHex({
r: (r * a) / 100 + (bgColor.r * (100 - a)) / 100,
g: (g * a) / 100 + (bgColor.g * (100 - a)) / 100,
b: (b * a) / 100 + (bgColor.b * (100 - a)) / 100
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17