前端工程师的色彩学:从色彩空间到无障碍配色
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:1 | 3:1 |
| AAA(增强) | 7:1 | 4.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),长时间阅读会导致视觉疲劳。推荐使用深灰色如 #1a1a2e 或 oklch(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 现代布局完全指南。