前端开发2026-03-24 11 分钟

前端工程师的色彩学:从色彩空间到无障碍配色

RGB、HSL、OKLCH...前端可用的颜色格式越来越多,到底该怎么选?如何确保你的配色方案对色觉障碍用户也友好?

之前做一个数据看板,我很得意地用了一组「好看的」颜色来区分图表的 6 条数据线。上线后收到用户反馈:「这几条线颜色都一样啊?」我一头雾水,后来才知道那位用户有红绿色觉障碍。全球约 8% 的男性有不同程度的色觉异常,这不是小众需求。那次经历让我意识到,前端工程师需要懂点色彩学。

CSS 的颜色系统简直是历史包袱大赏。先有 Hex,但不支持透明度(后来加了 8 位 Hex,但谁记得住 #FF000080 是啥?)。然后有 HSL,看起来很直观,但同样亮度值的不同色相看起来完全不一样亮。现在又来了 OKLCH,终于在感知均匀性上做对了,但浏览器支持才刚稳定。每隔几年就要学一套新的颜色系统,真的卷。

1. RGB 与 Hex:最熟悉的老朋友

RGB(Red, Green, Blue)是屏幕显示的原生模型,每个通道 0-255。Hex 只是 RGB 的十六进制写法。

/* 这三种写法等价 */
color: rgb(255, 87, 51);
color: #FF5733;
color: #ff5733;

/* 带透明度 */
color: rgb(255 87 51 / 0.5);
color: #FF573380;  /* 最后两位是 alpha,80 = 50% */

RGB/Hex 的问题在于它不直观。看到 #3B82F6 你能想象出是什么颜色吗?它是 Tailwind 的 blue-500。更大的问题是:RGB 不是感知均匀的色彩空间,直接在 RGB 空间做插值或生成色板,结果往往不符合人眼预期。

2. HSL:对人类更友好的表达

HSL(Hue, Saturation, Lightness)用色相、饱和度、亮度来描述颜色,比 RGB 直观得多。

/* hsl(色相, 饱和度, 亮度) */
color: hsl(210, 100%, 50%);  /* 纯蓝色 */
color: hsl(210, 100%, 70%);  /* 浅蓝色(提高亮度) */
color: hsl(210, 50%, 50%);   /* 灰蓝色(降低饱和度) */

/* 生成同色系色板只需调整亮度 */
--blue-100: hsl(210, 100%, 95%);
--blue-300: hsl(210, 100%, 75%);
--blue-500: hsl(210, 100%, 50%);
--blue-700: hsl(210, 100%, 30%);
--blue-900: hsl(210, 100%, 15%);

HSL 的色相(Hue)是一个 0-360 度的色环:0 是红色,120 是绿色,240 是蓝色。调色板生成变得非常直观——固定饱和度和亮度,改变色相就能得到一组「风格一致」的颜色。

HSL 的致命缺陷:感知不均匀。hsl(60, 100%, 50%)(黄色)和 hsl(240, 100%, 50%)(蓝色)虽然亮度参数都是 50%,但黄色在人眼看来明显比蓝色亮得多。这意味着用 HSL 生成的色板,不同色相之间的「视觉亮度」不一致。

3. OKLCH:CSS 颜色的未来

OKLCH(OK Lightness Chroma Hue)是一种感知均匀的色彩空间。「感知均匀」意味着相同数值差异的两个颜色,在人眼看来变化幅度也是相同的。

/* oklch(亮度, 色度, 色相) */
/* 亮度 0-1,色度 0-0.4,色相 0-360 */
color: oklch(0.637 0.237 25.5);   /* 红色 */
color: oklch(0.637 0.237 145);     /* 绿色,同亮度同色度 */
color: oklch(0.637 0.237 265);     /* 蓝色,同亮度同色度 */

/* 这三个颜色看起来「一样亮」,这在 HSL 中做不到 */

OKLCH 的优势

  • • 感知均匀:相同亮度值的不同色相看起来一样亮
  • • 支持 P3 广色域:能表示比 sRGB 更鲜艳的颜色
  • • 生成色板更可预测:调整一个参数不会影响其他维度的感知
  • • 用于 color-mix() 时插值效果更自然

注意事项

  • • 数值不直观:需要工具辅助选色
  • • 不是所有设计工具都支持 OKLCH
  • • P3 色域的颜色在 sRGB 屏幕上会被裁剪
  • • 团队需要学习新的心智模型

用 OKLCH 生成感知均匀的色板

:root {
  /* 固定色相和色度,只调亮度 */
  --brand-50:  oklch(0.97 0.02 250);
  --brand-100: oklch(0.93 0.04 250);
  --brand-200: oklch(0.87 0.08 250);
  --brand-300: oklch(0.77 0.13 250);
  --brand-400: oklch(0.67 0.18 250);
  --brand-500: oklch(0.55 0.22 250);  /* 主色 */
  --brand-600: oklch(0.47 0.20 250);
  --brand-700: oklch(0.39 0.17 250);
  --brand-800: oklch(0.31 0.13 250);
  --brand-900: oklch(0.23 0.09 250);
}

4. CSS 颜色函数实战

CSS 新增了几个强大的颜色函数,让动态颜色处理不再需要 JavaScript。

color-mix():颜色混合

/* 将两种颜色按比例混合 */
background: color-mix(in oklch, var(--brand-500) 70%, white);

/* 生成 hover 状态的颜色 */
.btn:hover {
  /* 主色混入 20% 的黑色,变深 */
  background: color-mix(in oklch, var(--brand-500) 80%, black);
}

/* 生成半透明版本 */
border-color: color-mix(in srgb, var(--brand-500) 30%, transparent);

相对颜色语法(Relative Color Syntax)

:root {
  --brand: oklch(0.55 0.22 250);
}

.card {
  /* 基于主色生成浅色背景:提高亮度,降低色度 */
  background: oklch(from var(--brand) calc(l + 0.35) calc(c * 0.3) h);

  /* 基于主色生成深色文字:降低亮度 */
  color: oklch(from var(--brand) calc(l - 0.2) c h);

  /* 基于主色生成互补色:色相旋转 180 度 */
  border-color: oklch(from var(--brand) l c calc(h + 180));
}

light-dark():暗色模式适配

:root {
  color-scheme: light dark;
}

.card {
  /* 自动根据 color-scheme 切换 */
  background: light-dark(#ffffff, #1a1a2e);
  color: light-dark(#333333, #eeeeee);
  border-color: light-dark(
    oklch(0.9 0.02 250),
    oklch(0.3 0.02 250)
  );
}

5. 无障碍配色指南

WCAG(Web Content Accessibility Guidelines)对文本颜色的对比度有明确要求:

级别正文文本大号文本(18px+)
AA(最低要求)4.5:13:1
AAA(增强)7:14.5:1

规则一:不要只靠颜色传达信息

错误状态不能只用红色标识——同时使用图标(如 × 号)、文字描述、边框样式等多重信号。图表中不同数据线除了颜色不同,还应该使用不同的线型(实线、虚线、点线)。

规则二:避免「红配绿」作为对比信号

红绿色盲是最常见的色觉障碍。用绿色表示「成功」红色表示「失败」对这些用户来说几乎无法区分。替代方案:用蓝色/橙色,或者加上图标和文字。

规则三:用工具验证对比度

Chrome DevTools 自带对比度检查:在 Elements 面板选中元素,点击颜色值旁边的色块,会直接显示对比度数值和是否达标。也可以用在线工具如 WebAIM Contrast Checker。

用 CSS 自动保证对比度(实验性)

/* contrast-color() 函数自动选择黑色或白色中对比度更高的那个 */
/* 目前仅 Chrome 131+ 支持,但值得关注 */
.badge {
  background: var(--tag-color);
  color: contrast-color(var(--tag-color));
}

6. 暗色模式的配色策略

暗色模式不是简单地「反转颜色」。以下是几个关键原则:

避免纯黑背景

#000000 纯黑在 OLED 屏幕上会产生「拖影」效果,而且与白色文字的对比度过高(21:1),长时间阅读会导致视觉疲劳。推荐使用深灰色如 #1a1a2eoklch(0.15 0.02 260)

降低饱和度

亮色模式下好看的高饱和色彩在暗色背景上会显得刺眼。暗色模式下应该把品牌色的饱和度降低 10-20%,亮度提高一些。用 OKLCH 很容易实现:oklch(from var(--brand) calc(l + 0.1) calc(c * 0.8) h)

利用层级区分深度

暗色模式中用不同深度的灰色来表示层级关系。越「上层」的元素颜色越浅(更接近光源)。比如背景用 oklch(0.15 ...),卡片用 oklch(0.20 ...),弹窗用 oklch(0.25 ...)

7. 实用选色建议

  • 新项目用 OKLCH 定义色板:感知均匀性让色板扩展更可预测
  • 用 CSS 变量管理颜色:方便暗色模式切换和主题定制
  • 用 color-mix() 派生变体:hover、active、disabled 状态从主色派生,保持一致性
  • 每次选色后检查对比度:至少达到 WCAG AA 标准(4.5:1)
  • 用模拟器测试色觉障碍:Chrome DevTools → Rendering → Emulate vision deficiencies

相关阅读

对 CSS 现代特性感兴趣?推荐阅读 CSS Container Queries:终于不用再依赖视口宽度做响应式了,以及 CSS 现代布局完全指南