开发者色彩理论:Hex、RGB、HSL完全对比

8 min2026年5月19日

Hex vs RGB vs HSL:每种格式代表什么

Hex、RGB、HSL之间的争论并不在于哪个"更好"——它们只是同一颜色的三种写法。#FF6B35、rgb(255, 107, 53)和hsl(16, 100%, 60%)完全相同。真正的区别在于它们如何将颜色拆解成组成部分,这直接影响你阅读、修改和程序化生成颜色的难易程度。

Hex(#RRGGBB)把红、绿、蓝三个通道压缩到6位十六进制数字中。每对范围从00(无)到FF(最大值)。它足够紧凑也足够通用——所有浏览器、设计工具和API都能识别hex。缺点是:它对人类不友好。你能看出#2E86AB是中蓝色吗?大多数人做不到。Hex适合存储和传输,不适合思考颜色。

RGB(红、绿、蓝)本质上和hex一样,只是用十进制(每通道0-255)表示。rgb(46, 134, 171)比#2E86AB稍微好读一点——你能看出蓝色通道(171)最高,绿色(134)适中,红色(46)最低。但要回答"把这个颜色调亮20%"或"偏移到橙色方向"这类问题,还是很困难,因为RGB中没有明确的亮度和色相概念。

HSL(色相、饱和度、亮度)把颜色分成三个直观的维度。色相是色轮上的位置(0°=红,120°=绿,240°=蓝)。饱和度是鲜艳程度(0%=灰色,100%=最鲜艳)。亮度就是明暗(0%=黑色,50%=纯色,100%=白色)。想要更亮的版本?加大L。想要柔和的版本?降低S。想要互补色?H加180°。这就是HSL在程序化颜色操作中胜出的原因。

什么时候用哪种格式(实用规则)

用hex的场景:CSS变量、设计token、品牌色定义、API响应、数据库存储。它是最紧凑的文本表示(6-8个字符),而且到处都支持。设计师给你颜色规范时会用hex,你在配置文件里存颜色也该用hex。

用RGB的场景:Canvas操作、WebGL、图像处理,以及任何需要操作单独颜色通道的场景。Canvas API用的就是RGB。混合两种颜色在RGB中很直观(对每个通道取平均)。Alpha合成公式也用RGB值。如果你在做像素级操作,就用RGB思考。

用HSL的场景:生成调色板、创建hover/active状态、构建主题,以及任何用户需要选色的界面。"暗10%"在HSL里是轻而易举的事(L减10)。"同色但柔和"也很简单(降低S)。生成5个均匀分布的颜色同样简单(360°除以5作为色相值)。我们的color-picker工具会同时显示三种格式,方便你查看它们之间的对应关系。

用OKLCH的场景:需要感知均匀的颜色操作时(CSS Color Level 4新增)。HSL有个缺陷——hsl(60, 100%, 50%)(黄色)看起来比hsl(240, 100%, 50%)(蓝色)亮得多,尽管它们的L值相同。OKLCH通过感知均匀的亮度解决了这个问题。如果你在2026年构建设计系统,需要不同色相之间一致的感知亮度,OKLCH是正确选择。语法是oklch(lightness chroma hue),其中lightness为0-1,chroma约0-0.4,hue为0-360°。

/* 同一颜色的不同格式 */
.button {
  /* 以下全部相同:一个暖橙色 */
  color: #FF6B35;
  color: rgb(255, 107, 53);
  color: hsl(16, 100%, 60%);
  color: oklch(0.7 0.18 45);
}

/* HSL让变体生成变得轻而易举 */
.button:hover {
  /* 暗10%:只需降低亮度 */
  background: hsl(16, 100%, 50%);
}
.button:active {
  /* 暗20% */
  background: hsl(16, 100%, 40%);
}
.button--muted {
  /* 同色相,低饱和度 */
  background: hsl(16, 40%, 60%);
}

/* 用HSL生成调色板 */
:root {
  --primary: hsl(220, 70%, 50%);
  --primary-light: hsl(220, 70%, 70%);
  --primary-dark: hsl(220, 70%, 30%);
  --complement: hsl(40, 70%, 50%); /* 色相+180° */
}

代码中的颜色操作

加深/提亮:在HSL中,直接减/加L值即可。hsl(200, 80%, 50%)加深20%就变成hsl(200, 80%, 30%)。如果用hex/RGB,你得先转HSL、修改、再转回去。这也是为什么每个CSS-in-JS库(styled-components、emotion)提供的darken()和lighten()辅助函数内部都是在HSL空间操作。

透明度:添加alpha通道。Hex用8位表示(#FF6B3580 = 50%透明度)。RGB变成rgba(255, 107, 53, 0.5)。HSL变成hsla(16, 100%, 60%, 0.5)。Alpha值从0(完全透明)到1(完全不透明)。注意:8位hex在IE11中不支持(如果你还在乎的话),但rgba()到处都能用。

颜色混合:最简单的方法是在RGB空间做线性插值。混合50%红色和50%蓝色:r=(255+0)/2=128, g=(0+0)/2=0, b=(0+255)/2=128 → rgb(128, 0, 128) = 紫色。CSS现在有原生的color-mix()函数:color-mix(in srgb, red 50%, blue)就能实现。如果要获得感知上更自然的混合效果,应该在OKLCH空间插值——RGB混合可能产生浑浊的中间色。

生成无障碍颜色对:计算前景色和背景色之间的WCAG对比度。公式使用相对亮度:L = 0.2126×R + 0.7152×G + 0.0722×B(其中R、G、B是从sRGB线性化后的值)。对比度 = (L1 + 0.05) / (L2 + 0.05),L1 > L2。WCAG AA要求普通文本4.5:1,大文本3:1。我们的color-picker工具会自动计算对比度。

CSS颜色函数(现代方案)

CSS相对颜色(2024+):color: hsl(from var(--brand) h s calc(l - 20%))可以不用JavaScript就创建品牌色的加深版本。"from"关键字将源颜色解构为各组成部分,然后你可以用calc()修改它们。这在很多场景下让预处理器的颜色函数变得多余了。

color-mix()(2023年起支持):color-mix(in oklch, var(--primary) 70%, white)将70%的主色与30%的白色混合,产生一个浅色调。"in oklch"部分指定插值使用的色彩空间——对大多数颜色组合来说,oklch比srgb产生更自然的混合效果。

oklch()函数:oklch(0.7 0.15 250)指定亮度(0-1)、色度(约0-0.4,颜色的鲜艳程度)和色相(0-360°)。和HSL不同,OKLCH中相同的亮度值在人眼看来确实一样亮。这对数据可视化很重要——如果你需要5种同等醒目的颜色,用OKLCH设置相同的L值就能做到,而HSL做不到。

实用建议:用hex定义设计token(兼容性好,设计师交接方便),但在CSS中用HSL或OKLCH来生成派生颜色(hover状态、禁用状态、焦点环)。这样两全其美:有设计师认得的稳定参考色,又能轻松地不借助JavaScript做程序化变体。

无障碍与对比度

WCAG 2.1对比度要求:普通文本(18px以下或14px bold以下)4.5:1,大文本(18px+或14px+ bold)3:1,UI组件和图形对象3:1。这些不是建议——在很多司法管辖区它们是法律要求(美国的ADA、欧盟的EN 301 549)。对比度不达标意味着某些用户真的看不清你的内容。

常见失败案例:白底上的浅灰文字(#999在#fff上 = 2.85:1,不通过AA)、太淡的placeholder文字、无法辨认的禁用按钮文字、与周围文字对比度不够的彩色链接。解决方法通常很简单——加深文字颜色或减淡背景色直到达到4.5:1。我们的color-picker工具在你调整颜色时会实时显示对比度。

暗色模式陷阱:简单反转颜色不能保持对比度。#333在#fff上是12.6:1(很好),反转后#ccc在#000上是13.1:1(也行)。但#666在#fff上是5.7:1(通过),反转后#999在#000上只有5.1:1——刚好通过,勉强及格。有些在浅色模式下通过的颜色组合,反转后在暗色模式会不通过。永远要为每种模式单独检查对比度。

超越WCAG:大约8%的男性和0.5%的女性有某种形式的色觉障碍。不要只用颜色传达信息(用红/绿表示错误/成功是经典的反面教材)。在颜色旁边使用图标、图案或文字标签。用色盲模拟器测试你的界面——Chrome DevTools内置了这个功能(Rendering面板 → Emulate vision deficiencies)。

超越sRGB的色彩空间

sRGB是Web的默认色彩空间,覆盖了可见色的约35%。现代显示器(P3色域,iPhone自2016年起使用,MacBook自2015年起使用)能显示比sRGB多约25%的颜色。如果你只用hex/rgb,就被限制在sRGB内。要使用更广的色域,在CSS中使用color(display-p3 1 0.5 0)。

什么时候广色域重要:鲜艳的摄影作品、在sRGB中看起来"平淡"的品牌色(尤其是饱和的红色和绿色)、以及任何想让颜色在支持的显示器上"跳出来"的设计。什么时候不重要:文字、UI边框、背景色,以及任何细微色差无关紧要的地方。

降级策略:用@supports (color: color(display-p3 1 0 0))检测P3支持,并提供sRGB降级方案。或者用color()函数加降级值:color: #ff6b35; color: color(display-p3 1 0.45 0.2);——不认识display-p3的浏览器会忽略第二条声明,使用hex值。

给非设计师出身的开发者:你大概率不需要考虑广色域。sRGB覆盖了典型UI设计中的所有颜色。广色域和摄影网站、电商产品图片、品牌导向的营销页面相关。如果设计师没有提到P3颜色,就在sRGB内用hex/rgb/hsl,把精力放在对比度上。广色域的性能开销为零——它只是CSS中不同的色彩空间声明,不是渲染负担。

Web开发中常见的颜色错误

错误1:硬编码颜色而不用CSS自定义属性。如果你的品牌蓝色出现在47个地方,设计师一改色,你就要做47次查找替换。把颜色定义一次:--color-primary: hsl(220, 70%, 50%),然后到处引用。这也让暗色模式变得简单——只需在.dark类或media query下重新定义自定义属性即可。

错误2:用opacity做hover状态而不是用HSL亮度。给彩色按钮设置opacity: 0.8会让背景色透过来,产生不可预期的颜色。正确做法是用同色系稍深/稍浅的版本:background: hsl(220, 70%, 45%)作为hover效果(暗5%)。这样不管元素后面是什么,结果都是一致的。

错误3:不在不同显示器上测试颜色。在你的MacBook Pro上(P3色域,高亮度)看起来很鲜艳的颜色,在便宜的办公显示器上(sRGB,低对比度)可能灰蒙蒙的。至少在一台非高端显示器上测试。经得住这个考验的颜色,才是用户真正会看到的颜色。

错误4:暗色模式中忽略颜色调整。简单地反转颜色或交换黑白背景会产生刺眼、让人疲劳的界面。好的暗色模式使用略微降低饱和度的颜色(HSL中S减10-20%)、用微妙灰度差异的抬升表面(不是纯黑)、以及大面积文字的降低对比度(纯白在纯黑上太刺眼——用#e0e0e0在#1a1a1a上代替)。