为什么开发者纷纷从 RGB 转向 HSL 来定义 UI 颜色

RGB与HSL色彩模型的并排对比,采用代码编辑器风格,附带色样展示

HSL 颜色模型 (色相、饱和度、亮度)为开发者提供了一种比 RGB 更直观的 CSS 颜色书写方式。你不再需要猜测红、绿、蓝三个通道的组合才能得到想要的颜色,HSL 让你用大脑本来思考颜色的方式来描述它:先选一个颜色,再决定它有多鲜艳,最后决定它有多亮或多暗。这种思维方式的转变,正是越来越多开发者放弃 rgb() 转而使用 hsl() 的原因。

RGB 与 HSL 的核心区别

RGB 通过混合红、绿、蓝三个光通道来定义颜色,每个通道的取值范围是 0 到 255。问题在于,这是屏幕渲染像素的方式,而不是人类感知颜色的方式。如果你想在 RGB 中让一个蓝色稍微亮一点,你必须以恰当的比例同时调整三个通道的值,没有一个单独的旋钮可以转动。

HSL 则直接对应人们在日常对话中描述颜色的方式:

  • 色相(Hue) - 颜色本身,以 360 度色轮上的角度表示。红色是 0 度,绿色是 120 度,蓝色是 240 度。
  • 饱和度(Saturation) - 颜色的鲜艳程度,从 0%(纯灰色)到 100%(完全鲜艳)。
  • 亮度(Lightness) - 颜色的明暗程度,从 0%(黑色)到 100%(白色),50% 为"纯正"颜色。

下面是同一种中等蓝色用两种格式表示的对比:

/* RGB - what do these numbers even mean at a glance? */
color: rgb(70, 130, 180);

/* HSL - instantly readable: blue hue, moderate saturation, medium lightness */
color: hsl(207, 44%, 49%);

两者都能呈现钢蓝色,但只有 HSL 版本让你一眼就能读懂信息。色相是 207(偏蓝),饱和度适中为 44%,亮度居中为 49%。不用打开取色器,你就能对这个颜色有清晰的认知。

属性 RGB HSL
人类可读性 很少
调亮/调暗颜色 需调整 3 个值 只调整亮度
改变饱和度 需重新计算全部 3 个值 只调整饱和度
创建色板 反复试错 按角度旋转色相
配合 CSS 变量进行主题化 冗长繁琐 简洁且可预测

HSL 在 CSS 中的语法

CSS 的 hsl() 函数自 CSS3 起就已得到支持。现代语法(CSS Color Level 4)还允许在 hsl() 内部直接传入第四个 alpha 参数,这使得 hsla() 在很大程度上已经多余:

/* Classic syntax */
color: hsl(207, 44%, 49%);

/* With alpha (transparency) - old way */
color: hsla(207, 44%, 49%, 0.8);

/* Modern CSS Color Level 4 syntax - commas optional, alpha with slash */
color: hsl(207 44% 49%);
color: hsl(207 44% 49% / 0.8);
color: hsl(207 44% 49% / 80%);

所有主流浏览器都支持这两种语法。无逗号版本是 CSS 的发展方向,但带逗号的版本在包括 Internet Explorer 9+ 在内的所有浏览器中均可使用。

为什么 HSL 更适合 UI 开发

HSL 的真正威力在你需要生成颜色变体的那一刻显现出来,而这在 UI 开发中几乎无处不在。

几秒内构建完整色板

假设你的品牌色是 hsl(140, 70%, 45%) 这样一个鲜艳的绿色。你需要一个悬停状态、一个禁用状态和一个浅色背景色调。在 HSL 中,每次只需改动一个值:

--color-base:     hsl(140, 70%, 45%);  /* base green */
--color-hover:    hsl(140, 70%, 38%);  /* darker - just lower lightness */
--color-disabled: hsl(140, 20%, 65%);  /* washed out - lower saturation */
--color-tint:     hsl(140, 70%, 92%);  /* very light background tint */

试试在不打开取色器的情况下用 RGB 做到同样的效果。那真的很难,因为没有一个独立的轴可以单独调整。

类似色与互补色

由于色相是色轮上的角度,生成和谐的色板就是简单的数学运算。类似色之间相差 30 度以内,互补色则相差 180 度:

--primary:       hsl(210, 80%, 50%);   /* blue */
--analogous-1:   hsl(180, 80%, 50%);   /* cyan - 30 degrees left */
--analogous-2:   hsl(240, 80%, 50%);   /* purple - 30 degrees right */
--complementary: hsl(30, 80%, 50%);    /* orange - 180 degrees opposite */

饱和度和亮度保持不变,因此这些颜色看起来像一个和谐的家族。这正是 Tailwind CSS 和 Material Design 等设计系统以编程方式生成色阶的原理。

实用 CSS 示例

将 HSL 各分量拆分为 CSS 自定义属性

最强大的 HSL 使用模式之一,是将三个值分别存入独立的 CSS 自定义属性。这样你可以在任何地方重新组合它们,并随时调整单个通道:

:root {
  --brand-h: 210;
  --brand-s: 80%;
  --brand-l: 50%;
  --brand-color: hsl(var(--brand-h), var(--brand-s), var(--brand-l));
}

.button {
  background-color: var(--brand-color);
}

.button:hover {
  /* Just override lightness - no need to redefine the whole color */
  background-color: hsl(var(--brand-h), var(--brand-s), 40%);
}

.button:disabled {
  background-color: hsl(var(--brand-h), 20%, 70%);
}
语法注意事项: hsl() 内部使用 CSS 自定义属性时,需要加逗号: hsl(var(--h), var(--s), var(--l)) 。目前并非所有浏览器都支持在 hsl() 内部使用 var() 的无逗号现代语法。

生成完整的色调色阶

设计系统通常需要一种颜色的 9 到 10 个色阶(类似 Tailwind 的 50 到 950 色阶)。使用 HSL,你可以在保持色相和饱和度不变的情况下,以固定间隔步进亮度来生成整套色阶:

:root {
  --blue-50:  hsl(210, 80%, 95%);
  --blue-100: hsl(210, 80%, 87%);
  --blue-200: hsl(210, 80%, 76%);
  --blue-300: hsl(210, 80%, 65%);
  --blue-400: hsl(210, 80%, 55%);
  --blue-500: hsl(210, 80%, 50%);  /* base */
  --blue-600: hsl(210, 80%, 43%);
  --blue-700: hsl(210, 80%, 36%);
  --blue-800: hsl(210, 80%, 26%);
  --blue-900: hsl(210, 80%, 16%);
}

如果你想深入了解各种颜色格式之间的关系, HEX 与 RGB 转换指南 全面介绍了 CSS 颜色格式的完整图景,包括何时该选用哪种格式。

悬停状态、主题化与暗色模式

悬停与焦点状态

HSL 让悬停状态的实现变得非常简单。你不需要单独定义一个全新的颜色,只需调整亮度即可:

.btn-primary {
  background: hsl(210, 80%, 50%);
  transition: background 0.2s ease;
}

.btn-primary:hover  { background: hsl(210, 80%, 43%); }
.btn-primary:active { background: hsl(210, 80%, 36%); }
.btn-primary:focus-visible {
  outline: 3px solid hsl(210, 80%, 70%);
}

每个状态都与基础颜色清晰关联。阅读这段代码的同事能立刻理解各状态之间的关系。

使用 HSL 变量实现主题化

HSL 是现代 CSS 主题化的核心。只需将色相暴露为一个变量,就能让用户或管理员通过修改一个数字来改变整个应用的配色方案:

/* Default theme: blue */
:root {
  --theme-hue: 210;
}

/* Green theme - just swap the hue */
[data-theme="green"] {
  --theme-hue: 140;
}

/* Purple theme */
[data-theme="purple"] {
  --theme-hue: 270;
}

/* All components use the same hue variable */
.button      { background: hsl(var(--theme-hue), 75%, 50%); }
.link        { color: hsl(var(--theme-hue), 75%, 40%); }
.badge       { background: hsl(var(--theme-hue), 75%, 92%); color: hsl(var(--theme-hue), 75%, 25%); }
.focus-ring  { outline-color: hsl(var(--theme-hue), 75%, 65%); }

用 HSL 实现暗色模式

暗色模式正是 HSL 大放异彩的场景。你不需要维护两套完全独立的色板,只需在同一色相下翻转亮度值:

:root {
  --bg:   hsl(210, 20%, 98%);   /* near-white background */
  --text: hsl(210, 20%, 15%);   /* near-black text */
  --card: hsl(210, 20%, 93%);   /* slightly darker card */
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:   hsl(210, 20%, 10%);  /* flip: near-black background */
    --text: hsl(210, 20%, 90%);  /* flip: near-white text */
    --card: hsl(210, 20%, 15%);  /* slightly lighter card in dark */
  }
}

注意,色相(210)和饱和度(20%)始终不变,只有亮度发生了翻转。这使暗色模式看起来像是同一套设计的反转版本,这正是优秀暗色模式应该呈现的效果。

浏览器支持与兼容性

HSL 的浏览器支持非常出色。 hsl() 函数自以下版本起就已可用:

  • Chrome 1(2008 年)
  • Firefox 1(2004 年)
  • Safari 3.1(2008 年)
  • Internet Explorer 9(2011 年)
  • Edge 12(2015 年)

现代无逗号语法( hsl(210 80% 50%) )和斜杠 alpha 语法( hsl(210 80% 50% / 0.5) )属于 CSS Color Level 4 规范 ,自 2023 年起已在所有现代浏览器中获得支持。如果你仍需支持 IE11(目前已较为罕见),请坚持使用带逗号的语法。

现在就可以放心使用: 带逗号的 hsl() 在用户可能使用的所有浏览器中均可正常运行。现代空格分隔语法在 Chrome 90+、Firefox 89+ 和 Safari 14.1+ 中均可使用,覆盖全球超过 95% 的浏览器用户。

你随时可以使用 取色器工具 在 HSL、RGB 和 HEX 格式之间相互转换,方便交叉核对颜色值,或将颜色交付给只接受特定格式的工具。

HSL 与 OKLCH - 下一步是什么?

如果你想在 HSL 之上更进一步, OKLCH 值得了解。它是一个感知均匀的色彩空间,意味着亮度或色度上相同的数值步长,在人眼看来也是相同的视觉变化,而 HSL 并不能完全保证这一点。

HSL 的问题在于,两个亮度值相同的颜色在感知亮度上可能差异很大。例如, hsl(60, 100%, 50%) (黄色)看起来比 hsl(240, 100%, 50%) (蓝色)亮得多,尽管两者的亮度都是 50%。OKLCH 纠正了这个问题。

/* HSL - same lightness, different perceived brightness */
color: hsl(60, 100%, 50%);   /* yellow - looks very bright */
color: hsl(240, 100%, 50%);  /* blue - looks much darker */

/* OKLCH - same lightness, actually looks the same to the eye */
color: oklch(0.75 0.18 90);   /* yellow-ish */
color: oklch(0.75 0.18 260);  /* blue-ish - genuinely similar perceived brightness */

OKLCH 还支持 广色域颜色 (P3 显示色域),超出了 HSL 所局限的 sRGB 范围。 oklch() 在 2024 年的浏览器支持已相当稳定:Chrome 111+、Firefox 113+、Safari 15.4+。

对于目前大多数项目而言,HSL 是最实用的选择:可读性强、易于维护、兼容性全面,相比 RGB 有着巨大的提升。当你在构建需要数学上一致的对比度比例的设计系统,或者需要针对广色域显示器时,OKLCH 才是更合适的选择。

如果你想在构建色板时以可视化方式探索颜色关系, 颜色探索工具 可以让你实时调试 HSL 值,观察色相旋转、饱和度变化和亮度调整如何相互影响。

HSL 取色器工具,展示色相、饱和度和亮度滑块

告别猜测,精准选取 HSL 颜色

我们的取色器让你通过可视化方式调整色相、饱和度和亮度,然后直接将 HSL 颜色模型的输出值复制到你的 CSS 中,无需任何心算。

立即试用取色器 →

你不需要做全面重写,但对于新写的颜色定义,尤其是在使用 CSS 自定义属性时,切换到 HSL 是值得的。收益最大的场景包括:构建或维护设计系统、创建悬停状态,以及实现暗色模式。在同一个项目中混用 RGB 和 HSL 是完全合法的 CSS 写法,浏览器处理两者时不存在任何性能差异。

没有任何实质性的性能差异。浏览器在解析时会将所有 CSS 颜色格式转换为内部表示,因此无论你写的是 hsl(207, 44%, 49%) 还是 rgb(70, 130, 180) ,渲染引擎在完成初始解析后对它们的处理方式完全相同。选择哪种格式完全取决于开发体验和可维护性,与运行时性能无关。

可以,而且这是 HSL 在 JavaScript 中最强大的应用场景之一。你可以将色相、饱和度和亮度分别存储为独立的数值,然后动态构建颜色字符串: element.style.color = `hsl(${hue}, ${saturation}%, ${lightness}%)` 。这使得动画、主题切换和交互式颜色控件的实现比使用 RGB 通道简单得多,因为后者需要转换计算才能达到同样的效果。

HSL(色相、饱和度、亮度)和 HSB/HSV(色相、饱和度、明度/色值)是不同的颜色模型,尽管它们共享色相轴。在 HSL 中,50% 的亮度对应纯正的完全饱和颜色;在 HSB 中,100% 的明度才对应纯正颜色。对于相同的饱和度和明度/亮度值,两种模型会产生不同的结果。CSS 使用的是 HSL,而 HSB/HSV 常见于 Photoshop 和 Figma 等设计工具,并非 CSS 原生格式。

从 HEX 到 HSL 的转换需要经过一个中间的 RGB 步骤:首先将 HEX 的每对十六进制数转换为 RGB 值(0-255),将其归一化到 0-1 范围,再套用 HSL 转换公式。实际上,大多数开发者会使用取色器工具、Figma 等设计工具,或浏览器开发者工具中的取色器来即时完成转换,而不是手动计算。浏览器开发者工具允许你点击任意颜色色块,在 HEX、RGB 和 HSL 表示之间循环切换。

是的,OKLCH 在无障碍访问工作中更可靠,因为它是感知均匀的,其亮度通道上相同的数值步长对应人眼感知到的相同亮度变化。这使得构建对比度在不同色相下都可预测的色板更加容易。使用 HSL 时,50% 亮度的黄色看起来比 50% 亮度的蓝色亮得多,这可能在无障碍访问上带来意外问题。对于符合 WCAG 对比度规范的场景,OKLCH 在构建无障碍色阶时能给你更可预测的结果。