项目开发中,需要根据网站设定的主题色来动态更改网站的所有配色,例如文字颜色,鼠标 hover 状态的按钮颜色,这就涉及到对颜色的相关处理,接下来我们就讲解下如何处理。
颜色的转换
颜色的表示方式目前主要有三种,rgba, hex 和 hls。这三种表示方式可以互相转换。主要是 hex 和 hls 进行转换的公式比较复杂,网上有现成的示例代码可以参考。
判断颜色是深色还是浅色
W3C 提供了官方的说明,把 RGB 格式的颜色转换成 YIQ 格式,然后通过 brightness 数值来进行判断。
ts
isDark() {
const { R, G, B } = this.rgbColor;
// ref https://www.w3.org/TR/AERT/#color-contrast
const brightness = (R * 299 + G * 587 + B * 114) / 1000;
return brightness < 128;
}计算对比度
W3C 提供了官方的对比度计算公式,对比度大于等于 4.5 属于 AA,对比度大于等于 7.1 属于 AAA。至少要达到 AA 才能有足够的对比度。
调节亮度
把颜色格式转换成 HLS, L 就代表亮度,调节这个数值就可以了。
完整代码
ts
export interface RGBColor {
R: number;
G: number;
B: number;
}
export interface HSLColor {
H: number;
S: number;
L: number;
}
export type ColorType = string | RGBColor | HSLColor;
export class Color {
rgbColor: RGBColor;
constructor(color: ColorType) {
if (this.isString(color)) {
this.rgbColor = this.stringToRGB(color);
} else if (this.isHSLColor(color)) {
this.rgbColor = this.HSLToRGB(color);
} else {
this.rgbColor = color;
}
}
get hexColor() {
return this.RGBToHex(this.rgbColor);
}
get hslColor() {
return this.RGBToHSL(this.rgbColor);
}
isString(color: string | RGBColor | HSLColor): color is string {
return typeof color === 'string';
}
isRGBColor(color: string | RGBColor | HSLColor): color is RGBColor {
return (color as RGBColor).R !== undefined;
}
isHSLColor(color: string | RGBColor | HSLColor): color is HSLColor {
return (color as HSLColor).H !== undefined;
}
RGBToHex(rgbColor: RGBColor) {
const { R, G, B } = rgbColor;
const hexR = R.toString(16).padStart(2, '0');
const hexG = G.toString(16).padStart(2, '0');
const hexB = B.toString(16).padStart(2, '0');
return '#' + hexR + hexG + hexB;
}
stringToRGB(color: string) {
if (color.length === 4) {
color =
color[0] +
color[1] +
color[1] +
color[2] +
color[2] +
color[3] +
color[3];
}
const R = Number.parseInt(color.substring(1, 3), 16);
const G = Number.parseInt(color.substring(3, 5), 16);
const B = Number.parseInt(color.substring(5, 7), 16);
return { R, G, B };
}
HSLToRGB(hslColor: HSLColor) {
let { S, L } = hslColor;
const { H } = hslColor;
S /= 100;
L /= 100;
const C = (1 - Math.abs(2 * L - 1)) * S;
const X = C * (1 - Math.abs(((H / 60) % 2) - 1));
const M = L - C / 2;
let R = 0;
let G = 0;
let 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;
}
R = Math.round((R + M) * 255);
G = Math.round((G + M) * 255);
B = Math.round((B + M) * 255);
return { R, G, B };
}
RGBToHSL(rgbColor: RGBColor) {
let { R, G, B } = rgbColor;
R /= 255;
G /= 255;
B /= 255;
const cmin = Math.min(R, G, B);
const cmax = Math.max(R, G, B);
const delta = cmax - cmin;
let H, S, L;
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 { H, S, L };
}
isDark() {
const { R, G, B } = this.rgbColor;
// ref https://www.w3.org/TR/AERT/#color-contrast
const brightness = (R * 299 + G * 587 + B * 114) / 1000;
return brightness < 128;
}
isLight() {
return !this.isDark();
}
darken(percent: number, color = this.hslColor) {
let { L } = color;
const { H, S } = color;
L -= L * percent;
return new Color({ H, S, L });
}
lighten(percent: number, color = this.hslColor) {
let { L } = color;
const { H, S } = color;
L += L * percent;
return new Color({ H, S, L });
}
luminance(rgbColor = this.rgbColor) {
// ref https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
let { R, G, B } = rgbColor;
[R, G, B] = [R, G, B].map(color => {
color = color / 255;
return color <= 0.03928
? color / 12.92
: Math.pow((color + 0.055) / 1.055, 2.4);
});
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
}
contrast(bgColor: ColorType = '#fff', contrastColor = this.rgbColor) {
const color = new Color(bgColor);
const bgLuminance = this.luminance(color.rgbColor);
const textLuminance = this.luminance(contrastColor);
return bgLuminance > textLuminance
? (bgLuminance + 0.05) / (textLuminance + 0.05)
: (textLuminance + 0.05) / (bgLuminance + 0.05);
}
level(bgColor: ColorType = '#fff') {
const color = new Color(bgColor);
const contrastRatio = this.contrast(color.rgbColor);
return contrastRatio >= 7.1 ? 'AAA' : contrastRatio >= 4.5 ? 'AA' : '';
}
highContrastColor(bgColor: ColorType = '#fff') {
let contrastColor = new Color(this.rgbColor);
if (this.isLight()) {
const color = new Color(bgColor);
let contrastRatio = this.contrast(color.rgbColor, contrastColor.rgbColor);
while (contrastRatio < 4.5) {
contrastColor = contrastColor.darken(0.1);
contrastRatio = this.contrast(color.rgbColor, contrastColor.rgbColor);
}
}
return contrastColor;
}
textColor() {
if (this.isLight()) {
return new Color('#000');
} else {
return new Color('#fff');
}
}
}本代码实现了颜色格式的转换,计算颜色属于深色还是浅色,计算两个颜色之间的对比度。给定一个颜色,计算出相应的高对比度颜色,可以用于背景和文本的展示。
总结
颜色相关计算有 W3C 提供了标准的计算公式,但是还没有完整的代码库可以参考。希望我总结的代码可以给大家提供参考。
参考
Converting Color Spaces in JavaScript
