/**
* @param {{r: number; g: number; b: number; a: number;}} color1
* @param {{r: number; g: number; b: number; a: number;}} color2
* @param {number} secondPercentage
*/
function colorMix(color1, color2, secondPercentage) {
const firstPercentage = 1 - secondPercentage;
// 1. 计算最终透明度
const alpha = color1.a * firstPercentage + color2.a * secondPercentage;
// 如果完全透明,直接返回透明黑色
if (alpha === 0) {
return { r: 0, g: 0, b: 0, a: 0 };
}
// 2. 计算每个颜色通道的"有效贡献值"
const red = (color1.r * color1.a * firstPercentage) + (color2.r * color2.a * secondPercentage);
const green = (color1.g * color1.a * firstPercentage) + (color2.g * color2.a * secondPercentage);
const blue = (color1.b * color1.a * firstPercentage) + (color2.b * color2.a * secondPercentage);
// 3. 用总贡献值除以最终透明度,得到实际颜色值
return {
r: Math.round(red / alpha),
g: Math.round(green / alpha),
b: Math.round(blue / alpha),
a: alpha
};
}
/** 百分比类 */
class Percentage {
/**
* @param {`${number}%`} value
*/
constructor(value) {
this.value = parseFloat(value) / 100;
}
tostring() {
return`${(this.value *100).toFixed(1)}%`;
}
}
/** 颜色基类 */
class Color {
/**
* @param {number} r
* @param {number} g
* @param {number} b
* @param {number} a
*/
constructor(r, g, b, a = 1) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
/**
* @param {string} str
*/
static parse(str) {
if(typeof str !== "string") throw new Error("必须是字符串");
str = str.trim();
// 解析RGB/RGBA
if (str.startsWith("rgb")) {
return Rgb.parse(str);
}
// 解析HSL/HSLA
if(str.startsWith("hsl")) {
return Hsl.parse(str);
}
// 解析HEX
if(str.startsWith('#')) {
return Rgb.parse(str);
}
throw new Error("不支持的颜色格式");
}
/**
* @param {Color} other
* @param {string} percentage
*/
mix(other, percentage) {
const secondPercentage = new Percentage(percentage).value;
const firstPercentage = 1 - secondPercentage;
// 1. 计算最终透明度
const alpha = this.a * firstPercentage + other.a * secondPercentage;
// 如果完全透明,直接返回透明黑色
if (alpha === 0) {
return { r: 0, g: 0, b: 0, a: 0 };
}
// 2. 计算每个颜色通道的"有效贡献值"
const red = (this.r * this.a * firstPercentage) + (other.r * other.a * secondPercentage);
const green = (this.g * this.a * firstPercentage) + (other.g * other.a * secondPercentage);
const blue = (this.b * this.a * firstPercentage) + (other.b * other.a * secondPercentage);
// 3. 用总贡献值除以最终透明度,得到实际颜色值
return new Rgb(
Math.round(red / alpha),
Math.round(green / alpha),
Math.round(blue / alpha),
alpha
);
}
}
/** RGB颜色类 */
class Rgb extends Color {
/**
* @param {string} str
*/
static parse(str) {
if(str.startsWith("#")) {
return this.parseHex(str);
}
if(str.startsWith("rgb")) {
return this.parseRgbString(str);
}
throw new Error("不支持的RGB格式");
}
/**
* @param {string} hex
*/
static parseHex(hex) {
hex = hex.replace('#', '');
let r, g, b, a = 1;
if (hex.length === 3 || hex.length === 4) {
r = parseInt(hex[0] + hex[0], 16);
g = parseInt(hex[1] + hex[1], 16);
b = parseInt(hex[2] + hex[2], 16);
if (hex.length === 4) a = parseInt(hex[3] + hex[3], 16) / 255;
} else {
r = parseInt(hex.substr(0, 2), 16);
g = parseInt(hex.substr(2, 2), 16);
b = parseInt(hex.substr(4, 2), 16);
if (hex.length === 8) a = parseInt(hex.substr(6, 2), 16) / 255;
}
return new Rgb(r, g, b, a);
}
/**
* @param {string} str
*/
static parseRgbString(str) {
const match = str.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
if (!match) throw new Error('无效的RGB格式');
return new Rgb(
parseInt(match[1]),
parseInt(match[2]),
parseInt(match[3]),
match[4] ? parseFloat(match[4]) : 1
);
}
toString() {
if (this.a === 1) {
return `rgb(${this.r}, ${this.g}, ${this.b})`;
}
return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a.toFixed(3)})`;
}
}
// HSL颜色类
class Hsl extends Color {
/**
* @param {number} h
* @param {number} s
* @param {number} l
* @param {number} a
*/
constructor(h, s, l, a = 1) {
// 转换为RGB存储
const rgb = Hsl.hslToRgb(h, s, l);
super(rgb.r, rgb.g, rgb.b, a);
this.h = h;
this.s = s;
this.l = l;
}
static parse(str) {
return this.hslStringToHsl(str);
}
/**
* @param {string} str
*/
static hslStringToHsl(str) {
const match = str.match(/hsla?\(([\d.]+),\s*([\d.]+)%,\s*([\d.]+)%(?:,\s*([\d.]+))?\)/);
if (!match) throw new Error('无效的HSL格式');
return new Hsl(
parseFloat(match[1]),
parseFloat(match[2]),
parseFloat(match[3]),
match[4] ? parseFloat(match[4]) : 1
);
}
/**
* @param {number} h
* @param {number} s
* @param {number} l
*/
static hslToRgb(h, s, l) {
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, g, b;
if (h < 60) [r, g, b] = [c, x, 0];
else if (h < 120) [r, g, b] = [x, c, 0];
else if (h < 180) [r, g, b] = [0, c, x];
else if (h < 240) [r, g, b] = [0, x, c];
else if (h < 300) [r, g, b] = [x, 0, c];
else [r, g, b] = [c, 0, x];
return {
r: Math.round((r + m) * 255),
g: Math.round((g + m) * 255),
b: Math.round((b + m) * 255)
};
}
toString() {
if (this.a === 1) {
return `hsl(${this.h}, ${this.s}%, ${this.l}%)`;
}
return `hsla(${this.h}, ${this.s}%, ${this.l}%, ${this.a})`;
}
}
/**
* @param {string} color1
* @param {string} color2
* @param {string} percentage
* @return {string}
*/
function colorMix(color1, color2, percentage) {
const c1 = Color.parse(color1);
const c2 = Color.parse(color2);
const mixed = c1.mix(c2, percentage);
return mixed.toString();
}