← 返回博客
语言: English 中文
Visual 2026-05-25 11 分钟

为什么 HSL 会误导:为可访问界面选择正确色彩空间

屏幕上的颜色,是 GPU 每像素发给面板的三个数字。代码里的颜色,是一个字符串。把字符串正确映射成那三个数字——这就是整件事的全部,也是 UI 工程里历史包袱最重的一个角落。CSS 在 2023 年发布了一个修复(`oklch()`),所有浏览器都已支持,而绝大多数样式表至今没在用。

颜色sRGBHSLOKLCHGammaWCAG色盲

颜色格式之所以让人困惑,是因为格式多——是因为它们各自对应不同任务,困惑发生在用错的格式做错的任务上。

这篇是田野指南。

屏幕上到底发生了什么

一个屏幕像素是三个子像素——红、绿、蓝。操作系统通过 GPU 把三个数字(每通道一个)发给面板。面板把数字转成光强。三个数字进,一个被感知的颜色出

这三个数字叫 RGB triple,GPU 默认期望的格式是 sRGB——除非下游链路有色彩管理介入。sRGB 是一个具体的色彩空间:它定义了面板会发出哪种红、哪种绿、哪种蓝,加上一条 gamma 曲线定义数值如何映射到光强。

本文剩下的所有内容,机理上都在讲一件事:怎样紧凑地编码一个 sRGB triple,让人能写出来。

Hex 和 RGB 是同一个东西

#ff0000#FF0000rgb(255, 0, 0)rgb(100%, 0%, 0%)——同一种颜色。Hex 就是用 16 进制写的 RGB,每通道两位。

三位 hex(#f00)是简写:每位重复两次,所以 #f00 = #ff0000#abc = #aabbcc。当每个通道高低半字节相同时方便。

四位 hex(#f00a)和八位 hex(#ff0000aa)末位是 alpha(透明度)。CSS 在 2018 年前后采纳,现代浏览器都能解析。#rrggbbaa 顺序是 RGBA——alpha 在最后——和 rgba(255, 0, 0, 0.667) 一致。别反过来写

Alpha 在 hex 中是 0(透明)到 255(不透明),在 rgba() 中是 01rgba(255, 0, 0, 0.5)#ff000080 是同一颜色:50% × 255 ≈ 128 = 0x80

RGB 数值本身是非线性的——见下面的"gamma"。这件事对色彩数学很重要;对挑颜色不太重要。

HSL 和 HSV:最常见的那个谎言

HSL(Hue / Saturation / Lightness)和 HSV/HSB(Hue / Saturation / Value)的设计目标是比 RGB 更直观。卖点:选色相(0–360°)、选饱和度、选亮度。问题:HSL 的 "lightness" 不对应"感知亮度"

自己证给自己看,CSS:

.a { background: hsl(60, 100%, 50%); }   /* 黄 */
.b { background: hsl(240, 100%, 50%); }  /* 蓝 */

两个 lightness: 50%。它们不是等亮的。50% 黄刺眼地亮;50% 蓝又暗又饱和。原因:HSL 的 "lightness" 是 RGB 空间里的数学中点,不是人眼感知的中点。黄本质上比蓝亮,HSL 没考虑这点。

这就是为什么"我用 HSL 的 lightness 步进生成调色板"那种项目产出的色卡看起来步距不均匀。lightness 滑块跟不上感知亮度,等步距 lightness 的色卡看起来不等亮

HSV(Photoshop、GIMP、许多取色器)在 value 上有同样问题。

HSL/HSV 适合:"想用人类可读的方式快速构造一个颜色"。不适合:感知色彩数学(混色、生成等距调色板、计算对比度、做"同一颜色稍微暗一点")。

Gamma:让像素看起来正确的曲线

rgb(0, 0, 0)rgb(255, 255, 255) 这串数字不是线性光强尺度。sRGB 色彩空间定义了一条gamma 曲线——大致 output = input^2.2——在你代码到面板之间的某一处被应用。

为什么:人眼是对数响应。我们对暗调比亮调更敏感。如果把 256 个亮度档位线性分配,暗的一半会有可见的色阶断层、亮的一半浪费分辨率。sRGB 的 gamma 曲线把更多编码值分给暗调——眼睛能注意到的地方。

后果:rgb(127, 127, 127) 不是 rgb(255, 255, 255) 一半亮。它大约是后者的 22%。sRGB 的"中点"在 rgb(186, 186, 186) 附近。

这件事在哪儿咬人:两个 RGB triple 取算术平均得不到感知中点。如果把红和绿在 RGB 空间里平均,得到的是浑浊的暗橄榄绿,不是你期待的鲜亮黄色。要做正确的色彩数学,必须先 sRGB → 线性 RGB,做计算,再转回去。

UI 代码里多数混色都跳过了这一步。结果就是渐变和暗色模式色彩系统那种"看着不太对劲"的感觉。浏览器在 2022 年通过 color-mix()interpolate-in-oklch 修了这件事——只要你显式选择感知色彩空间,数学就对。

OKLCH:现代答案

CSS Color Module 4 在 2022 年加入 oklch(),到 2024 年浏览器普遍支持。OKLCH 是感知色彩空间:

  • L(lightness):0 到 1,感知均匀。0.5 意味着"我能看到的最大亮度的一半"。
  • C(chroma):0 到约 0.4,色彩鲜艳度。0 是灰,越大越饱和。
  • H(hue):0 到 360°。

相对 HSL 的两大胜利:

  1. L 是感知的。 同一 L 下两个色,感知亮度等同,不论 hue 是什么。把 L 从 0.1 步到 0.9 生成 10 档调色板,肉眼看上去步距均匀
  2. 旋转 hue 时亮度保持。 在 HSL 里固定 lightness 改 hue 会改变感知亮度;OKLCH 不会。

代价:数值不像 HSL 那样直观可估。oklch(0.7 0.15 30)hsl(30, 80%, 50%) 难凭直觉读。用工具翻译。胜利在数学里,不在书写里。

CSS color-mix() 函数允许直接在 OKLCH 里插值:

background: color-mix(in oklch, blue 50%, red);

这个中点会看起来像真正的紫色,不是浑浊的灰。设计师手动重建这件事已经几十年了。

如果你 2026 年正在搭设计系统:把 token 定义在 OKLCH。需要时在构建步骤转 hex/RGB。下游所有事——调色板生成、暗色模式翻转、对比度检查——都会更干净。

CMYK:印刷那一个

Cyan(青)、Magenta(品)、Yellow(黄)、Key(黑)。减色法,用于印刷。每通道是 0% 到 100% 的油墨覆盖;四块印版按这个顺序印。

要理解两件事:

  1. CMYK 的色域比 sRGB 小。 屏幕上能看到的鲜亮蓝绿,印刷里不存在。从 sRGB 转 CMYK 是有损映射,转换需要决定如何处理超色域颜色(裁切 vs 降饱和)。
  2. CMYK 是设备相关的。** 不带 profile 的 "CMYK"(比如欧洲印刷的 FOGRA39、美国的 GRACoL)是没意义的。两台不同的印刷机给同一组 CMYK 数值会产出不同结果。

只做屏幕的工作就忽略 CMYK。需要印刷就用印刷厂指定的 profile,在 Photoshop 里软打样,并预期颜色比屏幕暗淡——没有"软件修复"能让 sRGB 里的颜色出现在 CMYK 里

WCAG 对比度:那条即将变化的规则

WCAG 定义了对比度公式,大致 (L1 + 0.05) / (L2 + 0.05)L1 是较亮颜色的相对亮度,L2 是较暗的。公式用 sRGB 颜色的相对亮度(线性 RGB 通道的加权和),是浏览器内置对比度检查器算的东西。

阈值:

  • 4.5:1 普通文本(WCAG AA)。
  • 3:1 大字号文本或 UI 组件(WCAG AA)。
  • 7:1 AAA 级普通文本。

公式是公认近似的。对黑底白字这种高对比组校准得好,对暗色模式的微妙搭配差,对色对色文本更差。下个大版本(WCAG 3.0,2026 年仍在制定)用 APCA(Accessible Perceptual Contrast Algorithm)替换它,APCA 用感知模型,对同样色对给出有时差异巨大的不同数值。

2026 年发布的工作:满足 WCAG 2.x AA 阈值,因为审计与采购按这个走。有余力同时检查 APCA,尤其暗色模式色对——WCAG 2 在那儿最宽松也最不准。

色盲

约 8% 男性和 0.5% 女性有某种颜色视觉缺陷。最常见是红绿混淆(deuteranomaly / protanomaly)。UI 上的关键结论:永远不要把颜色作为唯一信号。红错误和绿成功也应该图标 / 文字 / 位置不同。8 条线 8 种颜色的折线图也应该用线型或 marker 区分。

Coblis、浏览器 DevTools 里的色盲模拟器都很好用。它们是粗略代理——真实色觉缺陷个体差异大,重度患者看到的颜色非常不同——但能抓住常见失败模式。

具体规则:

  • 不要把红和绿用于对立含义而不附加其他区别特征。
  • 蓝/橙是好对比对,多数 CVD 类型都能看见。
  • 变 lightness,不只是 hue。 lightness 被稳定感知,hue 不一定。

常见坑

  • 用 RGB triple 计算"颜色相似度"。 用感知空间(OKLCH、Lab)。
  • 在 sRGB 里线性插值。 尤其渐变。用 OKLCH 或线性 RGB。
  • 用 HSL lightness 步进选调色板。 用 OKLCH 的 L。
  • 暗色模式信任 WCAG 2 对比度。 APCA 更接近真相。
  • CMYK 没带 profile。 选印刷厂正确的那个。
  • 三位 hex 想当四位 alpha 用。 #fff 是白,不是透明。
  • rgba(255, 0, 0, 0.5) 放暗色背景上。 结果看起来是暗红,不是粉红——alpha 与背景混合,不是与中间色调混合。
  • CSS 变量存 hex 字符串而不是分通道。 想用 color: rgb(var(--brand) / 0.5),把通道存成 --brand: 220 80 100;不是 --brand: #dc5064;
  • 不同 OS 颜色 profile 让浏览器用不同 sRGB 原色。 广色域显示器会比窄色域显示器更鲜艳地呈现 CSS sRGB 颜色,除非页面显式选 color(display-p3 ...)
  • 从截图取色而不检查截图的色彩 profile。2026 年 macOS 截图常常是 P3,不是 sRGB;粘进 sRGB 流程颜色就略变。

哪个用哪个

用途 格式
CSS 里临时挑一个颜色 hex(#dc5064
设计系统中的色彩 token OKLCH(oklch(0.7 0.15 30)
生成步进调色板 OKLCH(变 L)
在两个颜色之间插值 OKLCH 或线性 RGB
计算可访问性对比度 sRGB → 相对亮度 → WCAG ratio
印刷 带 profile 的 CMYK
程序化处理图像数据 线性 RGB
凭眼调整 "稍微亮一点"用 HSL 还行
macOS / iOS 广色域 color(display-p3 ...)

近十年的叙事弧线是:别再把颜色想成 RGB triple,把它想成一个感知坐标,再以消费方期望的格式表达。CSS 在 2022 年终于把工具原生地给了你。切到 OKLCH 的代价是构建管线里加一行;收益是设计 token 真的可组合、渐变看起来对、调色板不再和你作对。

主要参考资料

用于核对本文技术细节的标准与官方文档。

在所有色彩空间之间互转

本站颜色工具支持 hex、RGB、HSL、HSV、CMYK、OKLCH 在浏览器内互转,提供实时预览与对比度警告。适合在多种格式之间核对设计 token 是否一致。数据不会离开浏览器。

打开颜色工具

相关文章

继续阅读同一主题领域的实践指南。

查看全部文章

Cookie 同意

我们使用 Cookie 来增强您的体验并展示相关广告。您可以自定义您的偏好。