什么时候该查这张表这张表在你要把「设计稿里的颜色」翻译成「代码里的值」时有用:十六进制、RGB、HSL、CMYK 之间换算,以及 CSS 关键字与十六进制的对应关系,都是设计与实现之间最常出错的一步。
不该查的情况:你在做颜色对比度评估。WCAG 的亮度计算(相对亮度与对比度公式)有独立的一套推导,本页的数值转换不构成对比度结论。
判断标准:如果你在转换数值表示(HEX↔RGB↔HSL),本页够用;如果你在判断「这个配色能不能用」,需要的是 WCAG 公式与实际渲染后的取色,而不是同一个 hex 的两种写法。
特别注意 CMYK 是设备相关模型:同样的 CMYK 值在不同纸张、不同色彩配置下呈现不同。它用于印刷文件的头部声明(印刷界叫 ICC profile 引用),不能直接拿来做屏幕颜色。
边界与常见陷阱HEX 是 sRGB,不是「屏幕上的真实颜色」HEX 数值默认是 sRGB 色域下的坐标,而显示器是否准确呈现该色域取决于校色与观看环境。在广色域(P3)屏幕上,HEX 里没有任何一个值能落在 sRGB 之外;反过来 P3 的坐标写成 HEX 会明显偏淡。若产品需要利用广色域,应使用 color(display-p3 ...) 或 @media (color-gamut: p3) 做渐进增强。
HSL 的「百分比」是 sRGB 坐标系里的,不是感知均匀的HSL 适合「按色相挑一个看起来合理的颜色」这类探索(例如 24 色板、主题色生成),但不适合需要视觉一致微调的场景:同 L 值的不同色相在人眼看来亮度差别很大(纯黄 L=50% 看起来远亮于纯蓝 L=50%)。做明暗阶跃(hover/active 态)时应改用 HSL 的亮度调整配合实际观感,或直接用 LCH 之类感知均匀空间。
透明度叠在文字上会改变实际对比度rgba(0,0,0,0.5) 之类的半透明前景在深色背景上会与背景混合成一个新的、往往对比度不足的颜色。WCAG 的对比度必须针对「最终呈现的合成色」计算,而不是对 CSS 里声明的值。半透明文字还有一个副作用:不同浏览器与不同合成层级的取整差异会导致同一份代码在不同环境下可读性不同。
CSS 关键字颜色的实际值可能与直觉不同tomato 是 #FF6347、orange 是 #FFA500,但 gray 在 CSS 里是 #808080 而非某人口中的「深灰」,darkgray 是 #A9A9A9(比 gray 浅)。这些名字继承自 X11 早期色板,与日常语言直觉相反。需要精确控制时不要用关键字,直接写十六进制或引用设计令牌,避免「换个浏览器颜色就变」的隐患。
与相邻内容的分工要取图片主色、生成调色板或预览配色效果,用站内颜色相关工具;本页只做数值换算,不做图像分析。
判断配色是否可读(对比度、无障碍合规)需要 WCAG 相对亮度公式,本页不提供该结论。
需要把色值转成 CSS 变量或 tailwind 配置片段,可在站内转换器生成后自行落库,本页不提供代码生成。
若目的是保证深浅色模式一致,本表可帮助统一表示法,但明暗切换策略与对比度校验仍需另行设计。
实战片段要点:① HEX 是 sRGB 坐标,不是「看起来的颜色」,广色域需另用 color(display-p3) 渐进增强;② HSL 的 L 不是感知亮度——做对比度必须走 WCAG 相对亮度公式,代码里两个函数都在,务必用后者;③ d === 0 时直接返回 h=0/s=0,灰色没有色相,不要为了「凑一个值」去算色相。
// HEX ↔ RGB ↔ HSL 互转(不依赖任何库的最小实现)
function hexToRgb(hex: string) {
const h = hex.replace('#', '')
const full = h.length === 3 ? h.split('').map(c => c + c).join('') : h
return {
r: parseInt(full.slice(0, 2), 16),
g: parseInt(full.slice(2, 4), 16),
b: parseInt(full.slice(4, 6), 16),
}
}
function rgbToHsl({ r, g, b }: { r: number; g: number; b: number }) {
const [rn, gn, bn] = [r, g, b].map(v => v / 255)
const max = Math.max(rn, gn, bn), min = Math.min(rn, gn, bn)
const l = (max + min) / 2
const d = max - min
if (d === 0) return { h: 0, s: 0, l } // 灰色:色相无意义
const s = d / (1 - Math.abs(2 * l - 1))
let h: number
if (max === rn) h = 60 * (((gn - bn) / d) % 6)
else if (max === gn) h = 60 * ((bn - rn) / d + 2)
else h = 60 * ((rn - gn) / d + 4)
return { h: (h + 360) % 360, s, l }
}
// 相对亮度(WCAG 2.x)—— 对比度必须用它,而不是拿 HSL 的 L 当亮度
function relativeLuminance(hex: string) {
const { r, g, b } = hexToRgb(hex)
const lin = (v: number) => {
const c = v / 255
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
}
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b)
}
const contrast = (a: string, b: string) => {
const [x, y] = [relativeLuminance(a), relativeLuminance(b)].sort((p, q) => q - p)
return (x + 0.05) / (y + 0.05)
}
console.log(contrast('#0d9488', '#ffffff')) // 码屋 teal 对白字的实际对比度