colorMix 计算逻辑如下

/**
 * @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();
}

  

Posted on 2025-12-31 16:27  荆楚尬聊娃  阅读(0)  评论(0)    收藏  举报