ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

颜色代码从入门到实战:HEX、RGB、HSL、CMYK与Design Token全解析

颜色代码从入门到实战:HEX、RGB、HSL、CMYK与Design Token全解析 我在第一次把设计稿里的颜色发给前端时问了一句“这个红是什么红”对方回了一串“#E60039”。那段对话我记到现在因为后来才发现问题不在于我描述得够不够细而在于我们之间缺一套双方都能精确复现的颜色语言。颜色对应的代码说白了就是把人类眼睛看到的东西翻译成机器能理解、能存储、能传输的数字字符串。这篇文章我会从最基础的HEX讲起把RGB、HSL、CMYK这些常用“颜色字典”的原理和选择逻辑拆开聊再结合我在实际项目里反复踩过的坑——比如透明度叠加、色域偏差、对比度计算、Design Token落地——把颜色代码相关的经验一次性讲透。无论你是刚开始接触前端的设计新手还是已经写了几年CSS的开发应该都能从中找到能直接用起来的东西。1. 肉眼认色和机器认色差距在哪颜色代码的本质1.1 我们的眼睛“看见”颜色其实是在做三路加法人眼视网膜上有三种类型的视锥细胞分别对长波、中波、短波的光敏感大致对应我们常说的红、绿、蓝。大脑把这三路信号按不同强度叠加起来最终形成我们对颜色的感知。所以你会发现很多显示设备也恰好用红、绿、蓝三种子像素拼出所有颜色这不是巧合而是对人眼生理结构的模仿。当你用取色器在屏幕上随便点一下看到的“颜色对应的代码”本质上是记录红、绿、蓝三个通道各自亮度的字符串。以#FF6B6B为例它拆开看就是红255、绿107、蓝107。把所有可能的数值组合排列出来就是显示器能表现的那一大片色彩空间。很多人以为颜色代码是某种“颜色命名表”其实不对。它更像是一组三维坐标R、G、B就是三个轴不同的数值组合对应不同位置也就对应不同颜色。理解了这个后面看HSL、CMYK、LAB都不会觉得陌生因为它们只是换了一套坐标体系来描述同一个点。1.2 #RRGGBB到底怎么拆一个十六进制色的完整解剖我第一次教新人看HEX色值的时候最喜欢用#FF6B6B做例子。FF是十六进制里的最大两位等于十进制255也就是红色通道拉满6B换成十进制是107第二个6B也是107。所以#FF6B6B对应的RGB就是rgb(255, 107, 107)一种偏珊瑚感的红色。为什么是两位十六进制而不是直接写0到255因为十六进制两位正好能表示0到255的全部整数。00是最小FF是最大256个等级三通道相乘就是16,777,216种颜色也就是我们常说的1600万色。这个数字也是8bit色深的极限。我建议新手养成一个习惯看到一个以#开头的六位字符串先用眼睛拆成三组心里默默算成RGB。拆多了你会发现像#FF0000这种一眼就知道是大红#0000FF是纯蓝#00FF00是纯绿而#FFFFFF和#000000就是黑白。一旦你不依赖取色器也能“读”色值跟设计、跟后端沟通都会顺畅很多因为你开口就能说出“这个色红色通道太高了降一点会更好看”这种具体的话。1.3 颜色代码不是唯一答案不同坐标体系都能描述同一种颜色刚开始接触颜色代码时很容易把“十六进制”等同于“颜色的代码”其实HEX只是最常见的一种表示法。同样的#FF6B6B写成RGB是rgb(255, 107, 107)写成HSL可能是hsl(0, 100%, 71%)写成CMYK又有另一组数值。不同体系适合不同场景没有绝对的好坏。就像描述“我家位置”可以说“XX路XX号”也可以说“北纬N度、东经E度”还可以说“地铁站出口往西200米”指的是同一个地方但不同场景下好用程度差很多。后面我会专门比较这几套“坐标体系”但这里先记住一个核心观点颜色代码的本质是坐标而不是名字。坐标体系不同表达方式就不同可转换、可换算不冲突。2. HEX、RGB、HSL、CMYK四套常用颜色代码之间怎么选2.1 四套“字典”快速对照我把日常开发里最常见的几套颜色代码做个总表放在一起对比方便你根据自己的场景判断该用哪套。体系基本格式通道含义典型场景易用程度HEX#RRGGBB / #RRGGBBAA红、绿、蓝、透明度前端样式、设计稿标注写起来短适合复制RGB / RGBArgb(255, 107, 107, 0.8)红、绿、蓝、透明度代码逻辑、Canvas、颜色转换直观但不够“人性化”HSL / HSLAhsl(0, 100%, 71%)色相、饱和度、亮度做主题色、调色板、运行时计算最容易理解调起来最顺手CMYKcmyk(0, 58%, 58%, 0)青、品红、黄、黑印刷品、海报、物料不适合屏幕设计转换损耗大从这张表能看出一个规律面向屏幕的项目HEX占主导涉及透明度的交互场景RGBA和八位HEX更实用得改“这个颜色再温和一点”的需求HSL最高效一旦要出印刷物料CMYK才是印刷厂真正认的语言。2.2 不同场景下我优先用哪一套做前端页面样式时我绝大多数情况写HEX理由是短、好复制、跟设计稿标注一致。设计稿里标了#1E90FF前端直接用同一串字符中间少一层转换就少一次出错机会。碰上需要透明度的场景我用RGBA或者八位HEX比如#1E90FF80后面的80就是百分百透明度的十六进制写法。但做主题色或者做自动配色的时候我会立刻切成HSL。举个例子想做一套同色系按钮用HEX写只能靠经验微调改一个值试一次用HSL就轻松很多——固定色相H不变只调整饱和度S和亮度L出来的颜色天然和谐因为它们的“色相”是同一个只是深浅浓淡不同。印刷物料则是另一套逻辑。CMYK是减色混合跟屏幕的加色混合完全不同。屏幕上的蓝色在印刷时如果直接按RGB转CMYK经常会出现严重偏色甚至会发灰。所以我一般会在交付印刷前用专业工具把关键色从CMYK模式重新校准一遍绝不直接拿HEX值往印刷文件里填。2.3 转换其实很简单十六进制和十进制的互算很多同学一听到颜色转换就头大其实核心就一行逻辑十六进制的两位数第一位乘以16第二位乘以1加起来就是十进制。FF就是15×16加15等于2556B就是6×16加11等于107。我用一个函数来演示HEX转RGB再演示RGB转HEX核心代码不算复杂function hexToRgb(hex) { const normalized hex.replace(#, ); const full normalized.length 3 ? normalized.split().map(c c c).join() : normalized; return { r: parseInt(full.slice(0, 2), 16), g: parseInt(full.slice(2, 4), 16), b: parseInt(full.slice(4, 6), 16) }; } function rgbToHex(r, g, b) { const clamp v Math.max(0, Math.min(255, Math.round(v))); const toHex v clamp(v).toString(16).padStart(2, 0).toUpperCase(); return #${toHex(r)}${toHex(g)}${toHex(b)}; }这些代码写多了以后你会发现转换本身不是难点难点是搞清楚“为什么需要转换”。RGB和HEX在屏幕上完全等价转换只是为了方便阅读、存储、传输。而RGB转HSL就涉及更复杂的运算因为HSL不是线性叠加的坐标体系它是把人的直觉感受拆成了色相、饱和度、亮度。好在现在有大量现成库可以用不必每次手写公式但理解原理后你才知道为什么从HSL调出来的颜色总是比较“听话”。3. 读代码时最容易被忽略的三个分量透明度、色域与显示误差3.1 Alpha不是“半透明”这么简单很多人在样式中写rgba(0, 0, 0, 0.5)以为这就是“把这个颜色变成一半浓度”其实严格来说alpha通道描述的是前景与背景的混合权重不是颜色本身的属性。叠加公式大概是最终像素 前景色 × alpha 背景色 × (1 - alpha)。这意味着同一个半透明黑色放在白色背景上和放在黑色背景上最终呈现的颜色代码是截然不同的。我见过不少人调试弹窗遮罩时反复调alpha数值却总感觉“哪儿不对”其实是因为没考虑到底下内容的明暗变化。深色背景加0.5透明遮罩效果可能只是轻微提暗白色背景上同样的透明度视觉冲击会强很多。更隐蔽的一个点是alpha通道的叠加不是线性视觉。人眼对暗部细节更敏感对亮部相对迟钝。所以两个alpha数值差0.05在某些背景上视觉差异明显在某些背景上又完全看不出来。你要是靠感觉调半天不如直接写个小工具在目标背景色上做预览。3.2 同一个HEX在不同屏幕上为何不是一个色色域与色彩管理这是我当年最困惑的问题为什么同一个#FF3B30在同事的显示器上看跟我这里完全两个感觉后来才明白颜色代码只是一个“逻辑值”真正显示到你眼前还要经过设备的色域映射。sRGB是目前Web世界的默认色域但现在的手机和很多专业显示器已经支持P3广色域。同样是#FF3B30在P3色域设备上红色发光强度更高在普通sRGB屏幕上会显得更“淡”一点。虽然数值完全没变最终射入你眼睛的光就是不一样。这也是为什么设计行业特别强调校色。如果你用的是普通显示器也没有校色仪那你在自己屏幕上调到满意的颜色很可能到了用户的手机上就不是那个味道。靠谱的做法是关键界面颜色尽量在主流设备上截图对比而不要只死盯自己的屏幕。颜色代码负责“逻辑准确”但物理呈现已经超出代码能控制的范围了。3.3 肉眼觉得“差不多”的色值计算起来可能差不少有时候设计稿里两个按钮一个#1E90FF一个#1E88FF肉眼几乎看不出差别。在代码层面它们只是蓝色通道差了一点这种微差在实际交互中完全没问题。但反过来还有另一种情况两个颜色肉眼看着接近代码上却差了好几个明度等级这时在某些光照条件下文本可读性就会大打折扣。我一般建议团队里定一个“最小色差”意识改色值是为了解决视觉问题不是为了改而改。如果两个颜色差异太小在普通屏幕上根本分不出那就不要浪费精力去做回归测试。而如果涉及文字可读性不要靠肉眼“我觉得能看清”要用后面会讲的对比度公式去算因为屏幕亮暗、用户观看角度、视力情况都会影响真实可读性。4. 看懂颜色代码之后真正值钱的能力从代码判断对比度和配色4.1 从HEX算出相对亮度不需要背公式理解原理即可颜色代码不只是拿来配色的它还可以直接用来判断“这个字放在这个背景上能不能看清”。W3C的WCAG标准里给了一个相对亮度的定义简单说就是先把RGB每个通道转换到线性空间再按人眼对不同波长的敏感程度加权求和。我第一次看到公式时也头疼但实际用的时候不用自己算得那么痛苦写个函数就行function luminance(hex) { const { r, g, b } hexToRgb(hex); const toLinear v { const c v / 255; return c 0.03928 ? c / 12.92 : Math.pow((c 0.055) / 1.055, 2.4); }; return 0.2126 * toLinear(r) 0.7152 * toLinear(g) 0.0722 * toLinear(b); }你会发现绿色通道占的权重最高0.7152因为人眼对绿色最敏感。这也是为什么大多数界面里的“主色”如果偏绿通常看起来会特别亮。4.2 用代码验证对比度WCAG阈值不是随便定的有了相对亮度对比度公式其实就是一个比值(L1 0.05) / (L2 0.05)其中L1是较亮颜色的亮度L2是较暗颜色的亮度。WCAG建议正文文本的对比度至少达到4.5:1大号文本可以放宽到3:1。我用几个常见配色算过前景背景对比度结论#333333#FFFFFF12.6:1很强日常正文无压力#888888#FFFFFF3.2:1大标题可以小字不建议#FFFFFF#1E90FF4.0:1接近阈值小字要谨慎#FFFFFF#FF6B6B3.1:1只能做装饰或大号展示这个表特别适合拿来跟设计师讨论。以前方案评审的时候常说“这个灰太浅了看不清”但谁也说不清到底浅到什么程度。后来我直接在评审前把对比度数字算出来拿着“3.2:1小字低于4.5”这种结论去谈讨论效率高很多。你可能觉得“我又不是无障碍专家要不要这么较真”我的观点是文本可读性是用户体验的基础不是加分项。尤其移动端用户在户外强光下看屏幕对比度不足的文字会非常难辨认。提前用代码卡一遍比上线后被用户反馈“看不清”再返工成本低得多。4.3 基于主色“演算”配色从色相偏移到邻近色、互补色很多不会配色的人以为要学一堆色彩理论实际操作中有一颗主色之后生成一套能用且不丑的色板是有固定套路的。我常用的方法是把主色转成HSL然后固定H色相只调整S饱和度和L亮度得到同色系色板。需要强调色时再把H加上或减去150度或者180度得到邻近色或互补色。这个逻辑在CSS自定义属性里非常好用:root { --color-primary: #1E90FF; --color-primary-light: hsl(210, 100%, 85%); --color-primary-dark: hsl(210, 100%, 35%); --color-accent: hsl(30, 100%, 55%); }这种做法的好处是你的设计拥有一种明显的“内部逻辑”。以后想整体换主题色只需要改一个--color-primary其他浅色、深色、强调色都会跟着变。要是你当初把所有颜色都写成硬编码的HEX换主题色等同于全线返工那个痛苦我体会过好几次。5. 这几年实战下来我最想提醒的几个颜色代码使用细节5.1 先定色板再谈设计颜色变量与Design Token的价值我见过很多项目一开始只是零星几个地方用到颜色就随手写了HEX。等到界面一多同一个“主题蓝”可能同时存在#1E90FF、#1D8FFF、#1F92FF三种相似写法肉眼分不出来但一旦要统一调整就会发现自己根本不确定哪些地方该一起改。后来我在项目里养成的习惯是所有颜色必须先收敛成语义化的变量比如--color-primary、--color-danger、--color-text-secondary这类名称。表面上看是给颜色起了个别名深层价值是让你把“物理色值”和“业务语义”解耦。以后品牌主色想从蓝色换成绿色只需要改一个变量的定义所有用到--color-primary的地方自动同步。这个思路落到代码层面有一点要注意不同变量哪怕暂时值相同也不要合并成一个变量。比如某个地方用了跟主色一样的颜色但语义是“价格标签背景”就应该定义成--color-price-bg而不是直接复用--color-primary。否则半年后你自己都会被语义混淆搞晕。5.2 常见色值漂移问题取色、压缩、缓存、格式颜色代码明明是一样的可实际显示出来的颜色总感觉有点不对这种问题我排查过好多次。最常见的是取色器采样到的颜色和你想复制的颜色根本不是同一个屏幕上的像素经过了系统色彩管理、分辨率缩放、甚至防蓝光滤镜的叠加你点到的已经是“被处理过”的像素值不是原始CSS里的色值。所以取色前先截图再在干净的图片上取色比直接对着屏幕点靠谱得多。另一个坑是CSS压缩不会改HEX值但很容易让人误以为“压缩导致偏色”的是图片。图片里的颜色一旦经过了色域转换或高压缩率编码出现色带的概率就会增加。代码里的颜色和图片里的对应颜色常常差上几个数值这属于正常的格式损耗。还有八位HEX的兼容性问题。像#1E90FF80这种写法在较老环境里可能不被识别。如果必须兼容我会老老实实用rgba(30, 144, 255, 0.5)别赌运行时环境一定够新。5.3 我现在的取色与校验工作流从选色到上线前检查最后分享一下我现在实际在用的流程。第一步设计稿里取色统一用HEX格式存到一个JSON文件里当作唯一事实源。第二步通过脚本把JSON里的色值生成CSS变量和主题配置避免手工复制粘贴出错。第三步是颜色校验我会写一个很小的检查脚本遍历全项目的颜色值自动检查是否有硬编码颜色落到组件里同时跑一遍文本对比度把低于4.5的按钮和文本组合列出来。这个流程听起来简单但真做起来能挡住绝大多数低级问题。以前每次改版本主题色总有人来问“这个色怎么没变”现在只需要确认JSON里的值改对了剩下交给构建流程。颜色代码就不再是一堆散落的字符串而是一套能被系统管理、被脚本校验、被所有人理解的基础资产。我自己还有个很土但非常有效的小习惯新拿到一个陌生色值先转成HSL看一眼色相H。H在0附近偏红在120附近偏绿在240附近偏蓝。看完H再判断这个颜色是不是符合预期比盯着HEX发呆快太多了。希望你也能把颜色代码当成一种可以推演的坐标语言来用而不是每次靠取色器“点一下试试看”。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进