颜色选择不只是挑一个十六进制字符串。选择器展示的是某种颜色表示,最终外观还受源 Profile、传递函数、透明度合成、显示器和用户设置影响。本文区分记法与视觉效果,并说明如何构建可在真实界面中验证的调色板。

目录

核心要点

  • RGB是加色模型:红、绿、蓝三原色混合产生其他颜色,用于屏幕显示
  • HSL是编辑控制:色相、饱和度和亮度便于调色,但 HSL 不是感知均匀空间
  • HEX是RGB的简写:十六进制格式是RGB值的紧凑表示
  • 透明度支持:RGBA和HSLA支持alpha通道,实现半透明效果
  • CSS变量:使用CSS自定义属性管理颜色,便于主题切换

颜色模型详解

RGB模型

RGB(Red, Green, Blue)是一种加色模型,通过红、绿、蓝三种光的不同强度组合来产生各种颜色。

特点:

  • 每个通道值范围:0-255
  • 总共可表示:256³ = 16,777,216种颜色
  • 适用于屏幕显示(发光设备)

颜色混合示例:

颜色 R G B
红色 255 0 0
绿色 0 255 0
蓝色 0 0 255
黄色 255 255 0
青色 0 255 255
品红 255 0 255
白色 255 255 255
黑色 0 0 0

HSL模型

HSL(Hue, Saturation, Lightness)是便于编辑的圆柱形表示,不是感知尺度。不同色相即使使用相同的 L,人眼感知的明暗也可能不同。

参数说明:

  • 色相(Hue):0-360度,表示颜色在色轮上的位置
    • 0°/360°:红色
    • 120°:绿色
    • 240°:蓝色
  • 饱和度(Saturation):0%-100%,颜色的鲜艳程度
    • 0%:灰色
    • 100%:纯色
  • 亮度(Lightness):0%-100%,颜色的明暗程度
    • 0%:黑色
    • 50%:纯色
    • 100%:白色

HSV/HSB模型

HSV(Hue, Saturation, Value)也称为HSB(Hue, Saturation, Brightness),与HSL类似但亮度定义不同。

与HSL的区别:

  • HSL中50%亮度是纯色,100%是白色
  • HSV中100%明度是纯色,降低饱和度趋向白色

CMYK模型

CMYK(Cyan, Magenta, Yellow, Key/Black)是减色模型,主要用于印刷。

特点:

  • 青、品红、黄、黑四种油墨
  • 适用于印刷品(反射光)
  • Web开发中较少使用

Web颜色格式

HEX格式

在 CSS 中,十六进制记法表示 sRGB 通道值,可以使用 3、4、6 或 8 位十六进制数;4/8 位格式包含 alpha。

css
/* 6位格式 */
color: #FF5733;
color: #ffffff;
color: #000000;

/* 3位简写(每位重复) */
color: #F53;  /* 等同于 #FF5533 */
color: #fff;  /* 等同于 #ffffff */

/* 8位格式(带透明度) */
color: #FF573380;  /* 最后两位是透明度 */

RGB/RGBA格式

css
/* RGB格式 */
color: rgb(255, 87, 51);
color: rgb(0, 0, 0);

/* RGBA格式(带透明度) */
color: rgba(255, 87, 51, 0.5);
color: rgba(0, 0, 0, 0.8);

/* 现代CSS语法(空格分隔) */
color: rgb(255 87 51);
color: rgb(255 87 51 / 50%);

HSL/HSLA格式

css
/* HSL格式 */
color: hsl(11, 100%, 60%);
color: hsl(0, 0%, 0%);

/* HSLA格式(带透明度) */
color: hsla(11, 100%, 60%, 0.5);

/* 现代CSS语法 */
color: hsl(11 100% 60%);
color: hsl(11 100% 60% / 50%);

颜色关键字

CSS 预定义了一组颜色关键字:

css
color: red;
color: blue;
color: green;
color: transparent;
color: currentColor;

/* 常用颜色关键字 */
color: white;      /* #ffffff */
color: black;      /* #000000 */
color: gray;       /* #808080 */
color: silver;     /* #c0c0c0 */
color: navy;       /* #000080 */
color: teal;       /* #008080 */
color: coral;      /* #ff7f50 */

颜色格式转换

HEX转RGB

javascript
function hexToRgb(hex) {
  const value = String(hex).replace(/^#/, '');
  const expanded = value.length === 3 || value.length === 4
    ? [...value].map(char => char + char).join('')
    : value;
  if (!/^[\da-f]{6}([\da-f]{2})?$/i.test(expanded)) {
    throw new RangeError('需要 #RGB、#RGBA、#RRGGBB 或 #RRGGBBAA');
  }
  return {
    r: parseInt(expanded.slice(0, 2), 16),
    g: parseInt(expanded.slice(2, 4), 16),
    b: parseInt(expanded.slice(4, 6), 16),
    ...(expanded.length === 8
      ? { a: parseInt(expanded.slice(6, 8), 16) / 255 }
      : { a: 1 })
  };
}

// 使用示例
hexToRgb('#FF5733'); // { r: 255, g: 87, b: 51 }

RGB转HEX

javascript
function rgbToHex(r, g, b) {
  const channels = [r, g, b];
  if (!channels.every(Number.isInteger) ||
      channels.some(channel => channel < 0 || channel > 255)) {
    throw new RangeError('RGB 通道必须是 0 到 255 的整数');
  }
  return '#' + [r, g, b].map(x => {
    const hex = x.toString(16);
    return hex.length === 1 ? '0' + hex : hex;
  }).join('');
}

// 使用示例
rgbToHex(255, 87, 51); // '#ff5733'

RGB转HSL

javascript
function rgbToHsl(r, g, b) {
  r /= 255; g /= 255; b /= 255;
  const max = Math.max(r, g, b), min = Math.min(r, g, b);
  let h, s, l = (max + min) / 2;

  if (max === min) {
    h = s = 0;
  } else {
    const d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    switch (max) {
      case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
      case g: h = ((b - r) / d + 2) / 6; break;
      case b: h = ((r - g) / d + 4) / 6; break;
    }
  }

  return {
    h: Math.round(h * 360),
    s: Math.round(s * 100),
    l: Math.round(l * 100)
  };
}

// 使用示例
rgbToHsl(255, 87, 51); // { h: 11, s: 100, l: 60 }

代码示例

CSS变量管理颜色

css
:root {
  /* 主色调 */
  --color-primary: #3498db;
  --color-primary-light: #5dade2;
  --color-primary-dark: #2980b9;
  
  /* 辅助色 */
  --color-secondary: #2ecc71;
  --color-accent: #e74c3c;
  
  /* 中性色 */
  --color-text: #333333;
  --color-text-light: #666666;
  --color-background: #ffffff;
  --color-border: #e0e0e0;
  
  /* 语义色 */
  --color-success: #27ae60;
  --color-warning: #f39c12;
  --color-error: #e74c3c;
  --color-info: #3498db;
}

/* 使用变量 */
.button-primary {
  background-color: var(--color-primary);
  color: white;
}

.button-primary:hover {
  background-color: var(--color-primary-dark);
}

/* 深色主题 */
[data-theme="dark"] {
  --color-text: #ffffff;
  --color-background: #1a1a1a;
  --color-border: #333333;
}

JavaScript颜色操作

javascript
class Color {
  constructor(r, g, b, a = 1) {
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = a;
  }

  static fromHex(hex) {
    const value = hexToRgb(hex);
    return new Color(value.r, value.g, value.b, value.a);
  }

  toHex() {
    const toHex = n => n.toString(16).padStart(2, '0');
    return `#${toHex(this.r)}${toHex(this.g)}${toHex(this.b)}`;
  }

  toRgb() {
    return `rgb(${this.r}, ${this.g}, ${this.b})`;
  }

  toRgba() {
    return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`;
  }

  lighten(percent) {
    if (!Number.isFinite(percent) || percent < 0 || percent > 100) {
      throw new RangeError('percent 必须在 0 到 100 之间');
    }
    // 这里只演示通道算术;生产调色应考虑感知均匀空间。
    const amount = Math.round(255 * percent / 100);
    return new Color(
      Math.min(255, this.r + amount),
      Math.min(255, this.g + amount),
      Math.min(255, this.b + amount),
      this.a
    );
  }

  darken(percent) {
    const amount = Math.round(255 * percent / 100);
    return new Color(
      Math.max(0, this.r - amount),
      Math.max(0, this.g - amount),
      Math.max(0, this.b - amount),
      this.a
    );
  }
}

// 使用示例
const color = Color.fromHex('#3498db');
console.log(color.toRgb());           // rgb(52, 152, 219)
console.log(color.lighten(20).toHex()); // #6db8f3
console.log(color.darken(20).toHex());  // #0278ab

最佳实践

1. 使用语义化颜色命名

css
/* 推荐:语义化命名 */
--color-primary: #3498db;
--color-success: #27ae60;
--color-error: #e74c3c;

/* 不推荐:直接使用颜色名 */
--blue: #3498db;
--green: #27ae60;
--red: #e74c3c;

2. 建立颜色系统

css
/* 主色调的不同深浅 */
--color-primary-50: #e3f2fd;
--color-primary-100: #bbdefb;
--color-primary-200: #90caf9;
--color-primary-300: #64b5f6;
--color-primary-400: #42a5f5;
--color-primary-500: #2196f3;  /* 基础色 */
--color-primary-600: #1e88e5;
--color-primary-700: #1976d2;
--color-primary-800: #1565c0;
--color-primary-900: #0d47a1;

3. 确保对比度

javascript
// 计算对比度
function getContrastRatio(color1, color2) {
  const getLuminance = (r, g, b) => {
    const [rs, gs, bs] = [r, g, b].map(c => {
      c /= 255;
      return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
    });
    return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
  };
  
  const l1 = getLuminance(color1.r, color1.g, color1.b);
  const l2 = getLuminance(color2.r, color2.g, color2.b);
  const lighter = Math.max(l1, l2);
  const darker = Math.min(l1, l2);
  
  return (lighter + 0.05) / (darker + 0.05);
}

// WCAG 2.x AA 通常要求普通文本至少 4.5:1,大文本至少 3:1。
// “大文本”按渲染后的点数和字重定义,不是统一的 CSS 像素值。

4. 支持深色模式

css
/* 使用媒体查询自动切换 */
@media (prefers-color-scheme: dark) {
  :root {
    --color-text: #ffffff;
    --color-background: #121212;
  }
}

/* 或使用类切换 */
.dark-mode {
  --color-text: #ffffff;
  --color-background: #121212;
}

常见问题

HEX和RGB有什么区别?

HEX 是 RGB 通道值的十六进制表示,属于不同记法而非新的颜色模型;HEX 更紧凑,RGB 更便于表达通道和透明度。

什么时候使用HSL?

当需要调整颜色的亮度或饱和度时,HSL更方便。例如,创建同一色相的不同深浅变体。

如何选择合适的颜色对比度?

使用目标 sRGB 前景色和实际合成后的背景色计算,再验证真实组件。WCAG 比率不能替代焦点可见性、状态变化、图片、渐变和用户主题测试。

颜色选择器显示的是真实颜色吗?

不一定。颜色 Profile、显示器色域、亮度、环境光、浏览器行为和透明度合成都可能改变观感。处理图片时应保留 Profile 元数据,并把选择器值视为带假设的表示。

如何实现颜色主题切换?

使用CSS变量定义颜色,通过JavaScript切换根元素的类名或data属性来改变变量值。

总结

理解颜色模型和 Web 颜色格式有助于建立可维护的调色板,但单个十六进制值不能保证视觉效果或可访问性。应使用语义 Token,测试真实状态,并记录颜色空间与对比度假设。

快速总结:

  • RGB用于屏幕通道值,HSL便于编辑但不保证感知一致
  • HEX是RGB的紧凑表示,Web开发中最常用
  • 使用CSS变量管理颜色,便于主题切换
  • 确保文本和背景的对比度符合可访问性标准
  • 建立系统化的颜色方案,包含主色、辅助色和语义色

延伸阅读