工具指南13-在线颜色转换工具

5 阅读10分钟

做前端开发,和颜色打交道是家常便饭。设计师给你一个 HEX 值 #3B82F6,你要在 CSS 里用 rgba() 做半透明效果;PM 让你调整按钮颜色的亮度,你得在 HSL 色彩空间里操作才方便。颜色格式之间的来回转换,看似简单,实际容易出错——特别是当你需要在多个格式之间反复跳转的时候。

这篇文章会先梳理主流颜色格式的原理和适用场景,然后介绍如何用在线颜色转换工具提高日常开发效率。

颜色格式:不只是换个写法

很多人觉得 HEX、RGB、HSL 只是同一个颜色的不同写法。从结果看确实如此,但它们背后的设计哲学完全不同,这直接决定了各自的适用场景。

HEX:设计师的通用语言

HEX 格式(如 #3B82F6)本质上是 RGB 的十六进制压缩表示。每两位对应一个通道:3B = 红色 59, 82 = 绿色 130, F6 = 蓝色 246。

/* 6位标准写法 */
color: #3B82F6;

/* 8位写法(含透明度) */
color: #3B82F680; /* 最后两位 0x80 = 128, 128/255 ≈ 50% 透明度 */

/* 3位简写(每位重复) */
color: #F00; /* 等同于 #FF0000 */

HEX 的优势是紧凑,设计稿里到处都是它。但手动调色几乎不可能——你很难直观判断 #3B82F6#2563EB 亮了多少。

RGB / RGBA:机器的原生格式

RGB 直接对应显示器的三原色发光原理。每个通道取值 0-255,组合出约 1677 万种颜色。

/* 标准写法 */
color: rgb(59, 130, 246);

/* 带透明度 */
color: rgba(59, 130, 246, 0.5);

/* CSS Color Level 4 新语法 */
color: rgb(59 130 246 / 50%);

RGB 适合需要精确控制每个通道的场景,比如用 JavaScript 做像素级图像处理:

// Canvas 像素操作:灰度化
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
  const gray = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
  data[i] = gray;     // R
  data[i + 1] = gray; // G
  data[i + 2] = gray; // B
  // data[i + 3] 是 Alpha,保持不变
}
ctx.putImageData(imageData, 0, 0);

但 RGB 对人类不友好。"把这个蓝色调亮一点"——在 RGB 里你得同时调三个通道,而且调整比例还不能乱来,否则色相会偏移。

HSL:人类直觉的颜色模型

HSL 才是真正为人类设计的颜色格式。三个参数分别对应:

  • H (Hue):色相,0-360度的色环。0/360 = 红,120 = 绿,240 = 蓝
  • S (Saturation):饱和度,0% = 灰色,100% = 纯色
  • L (Lightness):亮度,0% = 黑色,50% = 纯色,100% = 白色
/* 标准写法 */
color: hsl(217, 91%, 60%);

/* 带透明度 */
color: hsla(217, 91%, 60%, 0.5);

/* CSS Color Level 4 新语法 */
color: hsl(217 91% 60% / 50%);

HSL 的直觉性体现在日常操作中:

/* 用 HSL 生成同色系的浅色/深色变体 */
:root {
  --primary: hsl(217, 91%, 60%);
  --primary-light: hsl(217, 91%, 75%);  /* 只改亮度 */
  --primary-dark: hsl(217, 91%, 45%);   /* 只改亮度 */
  --primary-muted: hsl(217, 40%, 60%);  /* 只改饱和度 */
}

同样的操作在 HEX 或 RGB 里要么靠猜,要么靠计算。这就是为什么现代 CSS 设计系统(如 Tailwind CSS 的调色板)内部基本都用 HSL 来生成色阶。

HWB:CSS 新成员

HWB(Hue-Whiteness-Blackness)是 CSS Color Level 4 引入的格式。逻辑更直觉:在一个色相上,加白变浅,加黑变深。

/* 主流浏览器已支持(Chrome 101+, Firefox 96+, Safari 15+) */
color: hwb(217 10% 10%);

HWB 的兼容性数据来源于 Can I Use。目前覆盖率已超过 90%,生产环境使用时建议提供 HSL 降级方案。

颜色转换的实际痛点

如果只是偶尔查一个颜色值,打开浏览器 DevTools 的取色器就够了。但以下场景会让你真正需要一个专门的转换工具:

场景一:设计系统的色板生成

你拿到设计师给的品牌色 #3B82F6,需要生成从 50 到 950 的完整色阶。基本思路是转成 HSL,固定色相,通过调整亮度生成不同深浅。Tailwind 等成熟设计系统在此基础上还会微调色相和饱和度(比如深色阶段色相会偏移几度,避免看起来发灰),所以最终的 HEX 值不是简单的亮度等差。以下是 Tailwind blue 色阶的参考值:

色阶大致 HSLHEX 值
50hsl(214, 100%, 97%)#EFF6FF
100hsl(214, 95%, 93%)#DBEAFE
200hsl(214, 97%, 87%)#BFDBFE
300hsl(214, 94%, 78%)#93C5FD
500hsl(217, 91%, 60%)#3B82F6
700hsl(224, 76%, 48%)#1D4ED8
900hsl(224, 64%, 33%)#1E3A8A

可以看到色相从 214 到 224 有几度偏移,饱和度也不完全一致。这些微调让色阶在视觉上更自然。无论如何,这个过程都需要在 HSL 和 HEX 之间反复转换,没有工具辅助不太现实。

场景二:暗色模式适配

暗色模式不是简单地反转颜色。通常的做法是:

  1. 把亮色模式的颜色转为 HSL
  2. 调整亮度(浅色变深,深色变浅)
  3. 适当降低饱和度(暗底上高饱和色会刺眼)
  4. 转回 HEX 或 RGB 写入代码
/* 亮色模式 */
:root {
  --bg: hsl(0, 0%, 100%);
  --text: hsl(217, 33%, 17%);
  --accent: hsl(217, 91%, 60%);
}

/* 暗色模式:调亮度 + 降饱和度 */
[data-theme="dark"] {
  --bg: hsl(222, 47%, 11%);
  --text: hsl(213, 31%, 91%);
  --accent: hsl(217, 78%, 65%); /* 饱和度从91%降到78%,亮度从60%提到65% */
}

场景三:无障碍对比度检查

WCAG 2.1 要求正文文本与背景的对比度至少 4.5:1(AA 级别),大号文本至少 3:1。对比度计算需要 RGB 值:

// 相对亮度计算(基于 WCAG 标准)
function relativeLuminance(r, g, b) {
  const [rs, gs, bs] = [r, g, b].map(c => {
    c = c / 255;
    return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
  });
  return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}

// 对比度计算
function contrastRatio(rgb1, rgb2) {
  const l1 = relativeLuminance(...rgb1);
  const l2 = relativeLuminance(...rgb2);
  const lighter = Math.max(l1, l2);
  const darker = Math.min(l1, l2);
  return (lighter + 0.05) / (darker + 0.05);
}

设计师给的是 HEX,WCAG 公式需要 RGB,你的 CSS 变量用的是 HSL。三种格式来回跳,没有工具辅助会很痛苦。

在线颜色转换工具实操

AnyFreeTools 的颜色转换工具覆盖了上面说的这些场景。输入任意格式的颜色值,实时转换为 HEX、RGB、HSL 等格式。

几个典型用法:

快速格式转换:在输入框填入 #3B82F6,立刻得到 rgb(59, 130, 246)hsl(217, 91%, 60%) 等所有格式的对应值,一键复制到代码里。

可视化调色:通过色板或滑块直接选色,比盲猜 HEX 值高效得多。特别是需要微调颜色时,拖动 HSL 的亮度滑块比手动改十六进制字符直观太多。

透明度处理:RGBA 和 HSLA 的透明度在设计到开发的交接中经常出问题。工具能同时显示带透明度和不带透明度的值,减少沟通成本。

所有计算在浏览器本地完成,颜色数据不会上传到服务器。这对处理未发布设计稿的配色方案时尤其重要。

颜色格式转换的底层原理

如果你对转换算法感兴趣,以下是核心逻辑(这也是大多数在线工具背后的实现):

HEX 转 RGB

最简单的转换,纯粹的进制转换:

function hexToRgb(hex: string): [number, number, number] {
  const h = hex.replace('#', '');
  // 处理3位简写
  const full = h.length === 3
    ? h.split('').map(c => c + c).join('')
    : h;
  return [
    parseInt(full.slice(0, 2), 16),
    parseInt(full.slice(2, 4), 16),
    parseInt(full.slice(4, 6), 16),
  ];
}

RGB 转 HSL

这个转换涉及色彩空间映射,稍微复杂一些:

function rgbToHsl(r: number, g: number, b: number): [number, number, number] {
  r /= 255; g /= 255; b /= 255;
  const max = Math.max(r, g, b);
  const min = Math.min(r, g, b);
  const l = (max + min) / 2;

  if (max === min) return [0, 0, l * 100];

  const d = max - min;
  const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);

  let h = 0;
  if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
  else if (max === g) h = ((b - r) / d + 2) / 6;
  else h = ((r - g) / d + 4) / 6;

  return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
}

算法的核心是找到 RGB 三通道的最大值和最小值。色相由最大通道决定(红/绿/蓝对应不同的计算公式),饱和度由最大值与最小值的差决定,亮度是最大值与最小值的平均值。

这段代码可以直接在浏览器里运行。注意 Math.round 会损失精度,如果需要链式转换(RGB → HSL → RGB),生产环境建议保留更多小数位再做最终取整。如果你在做组件库或设计系统,把这两个函数封装进工具函数是值得的。

实际开发中的选择建议

不同场景适合不同的颜色格式:

场景推荐格式原因
设计稿标注HEX紧凑,设计工具通用
CSS 变量 / 主题系统HSL方便生成色阶和变体
Canvas / WebGLRGB与底层 API 对应
需要透明度RGBA 或 HSLA显式表达 Alpha 通道
对比度计算RGBWCAG 公式基于 RGB
打印 / 印刷CMYK与印刷色域匹配

一个常见的实践是:设计系统的源头用 HSL 定义颜色变量,在需要的地方转成其他格式。CSS 自定义属性可以这样组织:

:root {
  /* 源头:HSL 分离变量,方便动态调整 */
  --blue-h: 217;
  --blue-s: 91%;
  --blue-l: 60%;

  /* 组合使用 */
  --blue-500: hsl(var(--blue-h), var(--blue-s), var(--blue-l));
  --blue-400: hsl(var(--blue-h), var(--blue-s), 68%);
  --blue-600: hsl(var(--blue-h), var(--blue-s), 52%);
}

这种写法的好处是,暗色模式只需要覆盖 --blue-l--blue-s,所有依赖这个颜色的组件自动更新。

小结

颜色转换看起来是个小需求,但它贯穿前端开发的各个环节——从设计稿还原、主题系统搭建到无障碍优化。理解不同格式的设计哲学(HEX 的紧凑、RGB 的精确、HSL 的直觉),能帮你在对的场景选对的工具。

日常开发中,与其每次手动查公式或写脚本转换,不如用在线颜色转换工具一步到位。省下来的时间去处理真正有挑战的问题。

本系列其他文章


原文链接chenguangliang.com/posts/blog1…