颜色代码转换与设计应用:HEX、RGB、HSL完全指南
前阵子接了个活,设计师在 Figma 里给我标的全是 HSL,说什么"主色稍微提一点亮度"。问题是项目里的 CSS 变量清一色 #3b82f6 这种 hex,设计稿和代码两套语言对不上号,我对着 hsl(217, 91%, 60%) 手算了半天,最后还是打开转换器糊弄过去的。更坑的是有一次我直接从 Figma 拷了个 hex 贴进 CSS,在我那台校色过的显示器上看着挺正,结果同事一个截图发过来——按钮上的白字几乎看不清。一查对比度,3.1,连 WCAG AA 都没摸到。
颜色这东西,平时不出问题你根本不会去想它,一出问题就是一连串:hex 和 HSL 来回换、rgba 的 alpha 在不同背景上渲染得不一样、设计稿的色值和实际渲染差那么一两个数。这篇就把我这几年踩过的坑、用顺手的转换算法和配色套路一次讲清楚。
几种格式,到底什么时候用哪个
刚入行时我以为这几种颜色格式是同一个东西的不同写法,换着用无所谓。后来发现差别大了去了,尤其是你要动态调色或者做主题的时候。
| 格式 | 适合场景 | 踩坑点 |
|---|---|---|
| HEX | 存储、设计稿交付、写死的固定色 | 想微调亮度只能靠肉眼试,#3b82f6 加深一点你根本算不出来是多少 |
| RGB / RGBA | 需要叠透明度、和 canvas/图像处理打交道 | alpha 在深色和浅色背景上观感完全不同,别只在白底上验收 |
| HSL / HSLA | 调色、生成同色系、做亮暗主题 | 它的"亮度50%"不是设计软件里那个明度,从 PS 抄数过来会偏 |
| OKLCH | 想要感知均匀的渐变、跨色相过渡不发灰 | 浏览器支持是近两年的事,老项目得留 fallback |
我现在的习惯是:存到代码里用 HEX,要动手调就先转成 HSL,渐变如果发灰再上 OKLCH。
目录
颜色模型基础
RGB颜色模型
RGB(Red, Green, Blue) 是最基础的颜色模型,通过红、绿、蓝三原色的组合产生各种颜色。
// RGB值范围:0-255
const red = { r: 255, g: 0, b: 0 };
const green = { r: 0, g: 255, b: 0 };
const blue = { r: 0, g: 0, b: 255 };
// 白色:所有通道最大值
const white = { r: 255, g: 255, b: 255 };
// 黑色:所有通道最小值
const black = { r: 0, g: 0, b: 0 };
// 灰色:三个通道值相等
const gray = { r: 128, g: 128, b: 128 };
为什么是0-255?
- 每个通道使用8位(1字节)存储
- 2^8 = 256种可能(0-255)
- RGB共24位,可表示16,777,216种颜色
HSL颜色模型
HSL(Hue, Saturation, Lightness) 更符合人类对颜色的感知:
-
Hue(色相):0-360度,表示颜色类型
- 0°/360° = 红色
- 60° = 黄色
- 120° = 绿色
- 180° = 青色
- 240° = 蓝色
- 300° = 品红
-
Saturation(饱和度):0-100%,表示颜色纯度
- 0% = 灰色
- 100% = 纯色
-
Lightness(亮度):0-100%,表示明暗
- 0% = 黑色
- 50% = 正常
- 100% = 白色
// HSL示例
const red = { h: 0, s: 100, l: 50 };
const pink = { h: 0, s: 100, l: 75 }; // 提高亮度
const darkRed = { h: 0, s: 100, l: 25 }; // 降低亮度
const desaturatedRed = { h: 0, s: 50, l: 50 }; // 降低饱和度
HSL的优势:
// 轻松生成同色系
function generateShades(baseHue) {
return [
`hsl(${baseHue}, 100%, 20%)`, // 深色
`hsl(${baseHue}, 100%, 40%)`,
`hsl(${baseHue}, 100%, 50%)`, // 标准色
`hsl(${baseHue}, 100%, 60%)`,
`hsl(${baseHue}, 100%, 80%)`, // 浅色
];
}
const blueShades = generateShades(240);
// ["hsl(240, 100%, 20%)", "hsl(240, 100%, 40%)", ...]
HSV/HSB颜色模型
HSV(Hue, Saturation, Value) 或 HSB(Hue, Saturation, Brightness):
- Hue:色相(同HSL)
- Saturation:饱和度(不同于HSL)
- Value/Brightness:明度
// HSV vs HSL
const color = {
hsl: { h: 0, s: 100, l: 50 }, // 纯红色
hsv: { h: 0, s: 100, v: 100 }, // 相同颜色的HSV表示
};
区别:
- HSL的亮度50%是正常颜色
- HSV的明度100%是正常颜色
- 设计软件(如Photoshop)常用HSV
颜色代码格式详解
HEX(十六进制)
格式:#RRGGBB 或 #RGB
/* 完整格式 */
#FF0000 /* 红色 */
#00FF00 /* 绿色 */
#0000FF /* 蓝色 */
/* 缩写格式(相同数字可省略) */
#F00 /* = #FF0000 */
#0F0 /* = #00FF00 */
#00F /* = #0000FF */
/* 带透明度(Alpha通道) */
#FF000080 /* 50%透明的红色 */
#F008 /* 缩写形式 */
十六进制转换:
function hexToRgb(hex) {
// 移除#号
hex = hex.replace(/^#/, '');
// 处理缩写格式
if (hex.length === 3) {
hex = hex
.split('')
.map((c) => c + c)
.join('');
}
// 解析RGB
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return { r, g, b };
}
console.log(hexToRgb('#FF5733')); // { r: 255, g: 87, b: 51 }
console.log(hexToRgb('#F00')); // { r: 255, g: 0, b: 0 }
function rgbToHex(r, g, b) {
const toHex = (n) => {
const hex = n.toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
}
console.log(rgbToHex(255, 87, 51)); // "#FF5733"
RGB/RGBA
/* RGB */
rgb(255, 0, 0) /* 红色 */
rgb(0, 255, 0) /* 绿色 */
rgb(0, 0, 255) /* 蓝色 */
/* RGBA(带透明度) */
rgba(255, 0, 0, 0.5) /* 50%透明的红色 */
rgba(0, 0, 0, 0.1) /* 10%透明的黑色 */
/* 现代CSS语法(可选透明度) */
rgb(255 0 0) /* 空格分隔 */
rgb(255 0 0 / 50%) /* 带透明度 */
HSL/HSLA
/* HSL */
hsl(0, 100%, 50%) /* 红色 */
hsl(120, 100%, 50%) /* 绿色 */
hsl(240, 100%, 50%) /* 蓝色 */
/* HSLA(带透明度) */
hsla(0, 100%, 50%, 0.5) /* 50%透明的红色 */
/* 现代语法 */
hsl(0 100% 50%)
hsl(0 100% 50% / 50%)
颜色关键字
/* CSS预定义颜色 */
red /* #FF0000 */
blue /* #0000FF */
green /* #008000(注意不是纯绿) */
white /* #FFFFFF */
black /* #000000 */
transparent /* 完全透明 */
/* 特殊值 */
currentColor /* 继承文本颜色 */
常用颜色关键字:
const namedColors = {
// 基础色
red: '#FF0000',
green: '#008000',
blue: '#0000FF',
yellow: '#FFFF00',
cyan: '#00FFFF',
magenta: '#FF00FF',
// 灰度
black: '#000000',
white: '#FFFFFF',
gray: '#808080',
silver: '#C0C0C0',
// 常用色
orange: '#FFA500',
purple: '#800080',
pink: '#FFC0CB',
brown: '#A52A2A',
};
颜色转换算法
RGB ↔ HEX
基础版上面写过了,这里补一个带透明度的——实际项目里 alpha 那一位经常忘了处理:
function rgbaToHex(r, g, b, a = 1) {
const toHex = (n) => {
const hex = Math.round(n).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
const alpha = Math.round(a * 255);
const hex = `#${toHex(r)}${toHex(g)}${toHex(b)}${toHex(alpha)}`;
return hex.toUpperCase();
}
console.log(rgbaToHex(255, 0, 0, 0.5)); // "#FF000080"
RGB ↔ HSL
function rgbToHsl(r, g, b) {
// 归一化到0-1
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const delta = max - min;
let h, s, l;
// 计算亮度
l = (max + min) / 2;
if (delta === 0) {
// 灰色
h = 0;
s = 0;
} else {
// 计算饱和度
s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
// 计算色相
switch (max) {
case r:
h = ((g - b) / delta + (g < b ? 6 : 0)) / 6;
break;
case g:
h = ((b - r) / delta + 2) / 6;
break;
case b:
h = ((r - g) / delta + 4) / 6;
break;
}
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
l: Math.round(l * 100),
};
}
console.log(rgbToHsl(255, 87, 51)); // { h: 11, s: 100, l: 60 }
function hslToRgb(h, s, l) {
// 归一化
h /= 360;
s /= 100;
l /= 100;
let r, g, b;
if (s === 0) {
// 灰色
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
};
}
console.log(hslToRgb(11, 100, 60)); // { r: 255, g: 87, b: 51 }
RGB ↔ HSV
function rgbToHsv(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const delta = max - min;
let h, s, v;
// Value(明度)
v = max;
// Saturation(饱和度)
s = max === 0 ? 0 : delta / max;
// Hue(色相)
if (delta === 0) {
h = 0;
} else {
switch (max) {
case r:
h = ((g - b) / delta + (g < b ? 6 : 0)) / 6;
break;
case g:
h = ((b - r) / delta + 2) / 6;
break;
case b:
h = ((r - g) / delta + 4) / 6;
break;
}
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
v: Math.round(v * 100),
};
}
配色原理与实践
色彩理论基础
1. 互补色(Complementary)
色环上相对的两种颜色:
function getComplementary(hue) {
return (hue + 180) % 360;
}
const baseHue = 30; // 橙色
const complementary = getComplementary(baseHue); // 210(蓝色)
console.log(`hsl(${baseHue}, 100%, 50%)`); // 橙色
console.log(`hsl(${complementary}, 100%, 50%)`); // 蓝色
2. 类似色(Analogous)
色环上相邻的颜色:
function getAnalogous(hue, angle = 30) {
return [(hue - angle + 360) % 360, hue, (hue + angle) % 360];
}
const analogous = getAnalogous(120); // 绿色为中心
// [90, 120, 150] - 黄绿、绿、青绿
3. 三色组(Triadic)
色环上等距的三种颜色:
function getTriadic(hue) {
return [hue, (hue + 120) % 360, (hue + 240) % 360];
}
const triadic = getTriadic(0); // 红色为起点
// [0, 120, 240] - 红、绿、蓝
4. 分裂互补色(Split-Complementary)
function getSplitComplementary(hue, angle = 150) {
const complement = (hue + 180) % 360;
return [hue, (complement - angle + 360) % 360, (complement + angle) % 360];
}
生成配色方案
class ColorScheme {
constructor(baseHue, saturation = 70, lightness = 50) {
this.baseHue = baseHue;
this.saturation = saturation;
this.lightness = lightness;
}
// 单色方案(不同明度)
monochromatic(steps = 5) {
const colors = [];
const lightStep = 80 / (steps - 1);
for (let i = 0; i < steps; i++) {
const l = 20 + lightStep * i;
colors.push(`hsl(${this.baseHue}, ${this.saturation}%, ${l}%)`);
}
return colors;
}
// 互补色方案
complementary() {
return [
`hsl(${this.baseHue}, ${this.saturation}%, ${this.lightness}%)`,
`hsl(${(this.baseHue + 180) % 360}, ${this.saturation}%, ${this.lightness}%)`,
];
}
// 类似色方案
analogous() {
return [
`hsl(${(this.baseHue - 30 + 360) % 360}, ${this.saturation}%, ${this.lightness}%)`,
`hsl(${this.baseHue}, ${this.saturation}%, ${this.lightness}%)`,
`hsl(${(this.baseHue + 30) % 360}, ${this.saturation}%, ${this.lightness}%)`,
];
}
// 三色组方案
triadic() {
return [
`hsl(${this.baseHue}, ${this.saturation}%, ${this.lightness}%)`,
`hsl(${(this.baseHue + 120) % 360}, ${this.saturation}%, ${this.lightness}%)`,
`hsl(${(this.baseHue + 240) % 360}, ${this.saturation}%, ${this.lightness}%)`,
];
}
}
// 使用
const scheme = new ColorScheme(210); // 蓝色为基础
console.log(scheme.monochromatic()); // 蓝色系深浅变化
console.log(scheme.complementary()); // 蓝色和橙色
console.log(scheme.triadic()); // 蓝、红、黄
Material Design配色
// Material Design风格的颜色生成
function generateMaterialPalette(baseHue) {
const palette = {
50: `hsl(${baseHue}, 100%, 97%)`,
100: `hsl(${baseHue}, 100%, 90%)`,
200: `hsl(${baseHue}, 100%, 80%)`,
300: `hsl(${baseHue}, 100%, 70%)`,
400: `hsl(${baseHue}, 100%, 60%)`,
500: `hsl(${baseHue}, 100%, 50%)`, // 主色
600: `hsl(${baseHue}, 100%, 45%)`,
700: `hsl(${baseHue}, 100%, 40%)`,
800: `hsl(${baseHue}, 100%, 30%)`,
900: `hsl(${baseHue}, 100%, 20%)`,
};
return palette;
}
const blue = generateMaterialPalette(210);
console.log(blue[500]); // 标准蓝色
console.log(blue[900]); // 深蓝色
CSS颜色应用
现代CSS颜色特性
/* CSS变量 */
:root {
--primary: #3b82f6;
--primary-rgb: 59, 130, 246;
--primary-hsl: 217, 91%, 60%;
}
.button {
background: var(--primary);
/* 使用rgb值添加透明度 */
box-shadow: 0 4px 6px rgba(var(--primary-rgb), 0.3);
}
/* CSS颜色函数 */
.card {
/* 相对颜色语法(CSS Color Module Level 5) */
background: hsl(from var(--primary) h s calc(l * 1.2));
}
/* 颜色混合 */
.mixed {
background: color-mix(in srgb, red 30%, blue);
}
动态主题切换
class ThemeManager {
constructor() {
this.themes = {
light: {
'--bg-primary': '#ffffff',
'--bg-secondary': '#f3f4f6',
'--text-primary': '#111827',
'--text-secondary': '#6b7280',
'--accent': '#3b82f6',
},
dark: {
'--bg-primary': '#111827',
'--bg-secondary': '#1f2937',
'--text-primary': '#f9fafb',
'--text-secondary': '#9ca3af',
'--accent': '#60a5fa',
},
};
}
setTheme(themeName) {
const theme = this.themes[themeName];
const root = document.documentElement;
Object.entries(theme).forEach(([key, value]) => {
root.style.setProperty(key, value);
});
}
// 根据基础色生成主题
generateTheme(primaryColor, mode = 'light') {
const { h, s, l } = rgbToHsl(...hexToRgb(primaryColor));
if (mode === 'light') {
return {
'--primary': `hsl(${h}, ${s}%, ${l}%)`,
'--primary-light': `hsl(${h}, ${s}%, ${Math.min(l + 20, 95)}%)`,
'--primary-dark': `hsl(${h}, ${s}%, ${Math.max(l - 20, 10)}%)`,
'--bg': `hsl(${h}, 10%, 98%)`,
'--text': `hsl(${h}, 10%, 10%)`,
};
} else {
return {
'--primary': `hsl(${h}, ${s}%, ${Math.min(l + 10, 70)}%)`,
'--primary-light': `hsl(${h}, ${s}%, ${Math.min(l + 30, 85)}%)`,
'--primary-dark': `hsl(${h}, ${s}%, ${Math.max(l - 10, 20)}%)`,
'--bg': `hsl(${h}, 10%, 10%)`,
'--text': `hsl(${h}, 10%, 98%)`,
};
}
}
}
// 使用
const themeManager = new ThemeManager();
themeManager.setTheme('dark');
// 或基于品牌色生成
const customTheme = themeManager.generateTheme('#3b82f6', 'light');
渐变色生成
function generateGradient(color1, color2, steps = 10) {
const rgb1 = hexToRgb(color1);
const rgb2 = hexToRgb(color2);
const colors = [];
for (let i = 0; i < steps; i++) {
const ratio = i / (steps - 1);
const r = Math.round(rgb1.r + (rgb2.r - rgb1.r) * ratio);
const g = Math.round(rgb1.g + (rgb2.g - rgb1.g) * ratio);
const b = Math.round(rgb1.b + (rgb2.b - rgb1.b) * ratio);
colors.push(rgbToHex(r, g, b));
}
return colors;
}
// 使用
const gradient = generateGradient('#FF0000', '#0000FF', 5);
// ["#FF0000", "#BF003F", "#7F007F", "#3F00BF", "#0000FF"]
// CSS渐变
const css = `linear-gradient(90deg, ${gradient.join(', ')})`;
实战案例
案例1:颜色选择器组件
class ColorPicker {
constructor(initialColor = '#000000') {
this.color = hexToRgb(initialColor);
this.listeners = [];
}
setRgb(r, g, b) {
this.color = { r, g, b };
this.notify();
}
setHex(hex) {
this.color = hexToRgb(hex);
this.notify();
}
setHsl(h, s, l) {
this.color = hslToRgb(h, s, l);
this.notify();
}
getHex() {
return rgbToHex(this.color.r, this.color.g, this.color.b);
}
getRgb() {
return this.color;
}
getHsl() {
return rgbToHsl(this.color.r, this.color.g, this.color.b);
}
onChange(callback) {
this.listeners.push(callback);
}
notify() {
this.listeners.forEach((callback) => callback(this.color));
}
}
// 使用
const picker = new ColorPicker('#3b82f6');
picker.onChange((color) => {
document.querySelector('#preview').style.background = picker.getHex();
document.querySelector('#hex').value = picker.getHex();
const hsl = picker.getHsl();
document.querySelector('#hsl').value = `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`;
});
案例2:无障碍对比度检查
// WCAG 2.0对比度计算
function getContrastRatio(color1, color2) {
const getLuminance = (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;
};
const rgb1 = typeof color1 === 'string' ? hexToRgb(color1) : color1;
const rgb2 = typeof color2 === 'string' ? hexToRgb(color2) : color2;
const lum1 = getLuminance(rgb1.r, rgb1.g, rgb1.b);
const lum2 = getLuminance(rgb2.r, rgb2.g, rgb2.b);
const brighter = Math.max(lum1, lum2);
const darker = Math.min(lum1, lum2);
return (brighter + 0.05) / (darker + 0.05);
}
function checkAccessibility(foreground, background) {
const ratio = getContrastRatio(foreground, background);
return {
ratio: ratio.toFixed(2),
AA_Normal: ratio >= 4.5, // 普通文字AA级
AA_Large: ratio >= 3, // 大文字AA级
AAA_Normal: ratio >= 7, // 普通文字AAA级
AAA_Large: ratio >= 4.5, // 大文字AAA级
};
}
// 使用
const result = checkAccessibility('#3b82f6', '#ffffff');
console.log(result);
// { ratio: "4.56", AA_Normal: true, AA_Large: true, AAA_Normal: false, AAA_Large: true }
案例3:智能文字颜色
// 根据背景色自动选择黑或白文字
function getTextColor(backgroundColor) {
const rgb = typeof backgroundColor === 'string' ? hexToRgb(backgroundColor) : backgroundColor;
// YIQ公式
const yiq = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
return yiq >= 128 ? '#000000' : '#ffffff';
}
// 使用
document.querySelectorAll('.tag').forEach((tag) => {
const bgColor = getComputedStyle(tag).backgroundColor;
tag.style.color = getTextColor(bgColor);
});
写在最后
如果只让我留一句话给刚开始折腾颜色的人,那就是:调色用 HSL,别用 hex 硬扛。#3b82f6 你看不出该怎么变深变浅,但 hsl(217, 91%, 60%) 你一眼就知道把那个 60% 调到 45% 就行——色相不动、饱和度不动,亮度往下拉,深一号的同色就出来了。我自己做主题那套生成函数,全是在 HSL 空间里玩的,换成 hex 根本没法写。
另一件我想反复强调的事:对比度。设计稿好看不等于能用。那个 3.1 的按钮在我显示器上挺漂亮,可它对一部分用户来说就是看不清。养成习惯,文字配背景算一下对比度,普通正文卡 4.5 这条线,这不是锦上添花,是底线。上面那个 getContrastRatio 函数我现在基本是随手就贴进项目的。
至于存储,老老实实用 HEX 或 RGB,兼容性最稳,要调的时候再转。OKLCH 很香但别急着全面铺开,老浏览器还得留后路。
哦对,顺带说一句——文章里这几个颜色工具(转换器、调色板、对比度检查)全是纯浏览器里跑的,你输的色值不会上传到任何服务器,连日志都没有。改色拾色这种事本来就该在本地完成,没必要让它出门。
颜色这块水挺深,但日常 90% 的场景,把上面这几条吃透就够用了。剩下的,遇到再说。
相关工具
关键词: 颜色转换, HEX, RGB, HSL, CSS颜色, 配色理论, 前端设计
更新时间: 2026-01-05
我们是一支开发者小团队,负责构建和维护 ToolsForge —— 150+ 个完全在浏览器本地运行、隐私优先的工具。这些文章来自我们日常的工程实践,而不是纸上谈兵。
了解我们相关阅读
一张照片能做多少事:AI 改图工具实战指南
换发型、换发色、换装、转卡通、风格迁移、室内设计、老照片修复——用一张照片走完这些 AI 改图流程,以及怎么让结果更稳、更像本人。
把脸保住:AI 改图提示词怎么写才不"换人"
换发型、换装、换风格时,AI 老把脸也改了?这篇讲清楚改图提示词的写法:保人约束、双通道(gpt-image-2 / Nano Banana)各自的脾气,以及一套能直接抄的模板。
TypeScript 类型体操:什么时候值,什么时候在炫技
务实的 TypeScript 高级类型指南 — mapped types、conditional types、template literal types 真正能给你什么,什么时候用,什么时候应该退回到朴素代码。