
入行前几年我一直没把颜色当回事。反正设计稿里写什么色值我照着填就是了——#ffffff填白色#000000填黑色中间那些花里胡哨的颜色直接从 Figma 里复制从来不深想。直到有一次接手一个老项目看到一个#FF7F50我愣是没看明白这是什么颜色上线的页面因为不同显示器上色差明显被设计师追着改了三轮。从那时候开始我才意识到CSS 里的十六进制颜色根本不是复制粘贴就完事的东西它背后是一整套色彩编码逻辑。把这套逻辑吃透你在写样式的时候速度、精度、稳定性都会上一个台阶。这篇文章就把我从原理到实战摸过的路完整梳理一遍适合刚接触 CSS 的前端新人也适合写了好几年样式但一直靠工具取色的同学。1. 为什么要用十六进制从命名色的局限说起1.1 你记不住的颜色名字我最早学 CSS 的时候觉得英文颜色名是最亲切的。red、blue、green写起来就像在说人话。可一旦脱离这几个高频词事情就开始变得尴尬aliceblue是什么蓝blanchedalmond又是什么杏仁rebeccapurple到底是个什么紫CSS 规范里定义了 148 个标准颜色关键字从black到whitesmoke都有。但问题在于这些命名颜色的背后每一个都映射着一个具体的 RGB 值。比如tomato这个颜色它的 RGB 值是rgb(255, 99, 71)对应的十六进制是#FF6347。当你只写一个tomato的时候你其实是在依赖浏览器帮你做名字到具体数值的转换。这个转换本身没问题问题是它不透明你没法基于名字做任何精确的调整——稍微偏黄一点、再深一点点这种需求一出来命名颜色就彻底没戏了。更麻烦的是记忆成本。你不可能记住 148 个颜色各自对应的 RGB 值也没必要。但十六进制的好处恰恰在于你只要理解了它的编码规则看一眼就能判断出这个颜色大概的红绿蓝分量是什么。如果再熟悉几个常见色值区间你甚至能直接推断出颜色的大致色相。1.2 十六进制其实是 RGB 的压缩写法要理解十六进制颜色核心就是记住一句话CSS 里的#RRGGBB本质上是红、绿、蓝三个通道的亮度值只是从十进制换成了十六进制表示。每个颜色通道的取值范围是 0-255也就是 256 个等级。256 这个数字很巧它正好等于 16 × 16。于是一个通道的亮度就可以用两位十六进制数来表示第一位代表16 的十位第二位代表个位。#FF0000就是 R255、G0、B0也就是纯红#00FF00是纯绿#0000FF是纯蓝。三个通道拼在一起表达一个完整的颜色。这比写rgba(255, 0, 0, 1)短但信息量完全一样。所以说到底十六进制颜色不是一个新概念它是 RGB 色彩模型在 CSS 里的一种序列化格式。理解了 RGB你就已经理解了十六进制颜色的百分之八十。2. 十六进制转 RGB 的数学逻辑手算一次就懂了2.1 从 #A5371F 到 rgb(165, 55, 31) 的完整推演很多人用十六进制颜色用了好几年但你要是问他#A5371F对应的 RGB 是多少他大概率要打开控制台或者取色器。其实这个转换过程非常简单就是小学级别的乘法和加法。我把步骤拆开写给你看。第一把一个 6 位十六进制颜色拆成三组两位数#A5 37 1F。这三个组分别就是 R、G、B 通道。第二每组两位数从十六进制转十进制。规则是第一位高位乘以 16加上第二位低位。如果遇到 A-F分别对应 10-15。拿第一组A5来算高位A 10低位5 5结果 10 × 16 5 165第二组37高位3 3低位7 7结果 3 × 16 7 55第三组1F高位1 1低位F 15结果 1 × 16 15 31所以#A5371Frgb(165, 55, 31)。这个颜色呈红棕色偏暖偏暗所以视觉上是一种带着沉稳感的红。反过来从rgb(165, 55, 31)转十六进制也是同样逻辑分别用 165 ÷ 16 10 余 5商对应A余数对应5得到A555 ÷ 16 3 余 7得到3731 ÷ 16 1 余 15得到1F。合起来就是A5371F。这一套算法正常人手动都不需要因为你不会真去笔算。但你掌握这个规则之后看十六进制色值就像看车牌号一样——#C0开头通常偏青#FF开头是亮色#33、#66这种中等亮度的通道数值也很眼熟。这种色值直觉就是靠理解规则一点点积累出来的。2.2 为什么偏偏是 256、16 和 FF一个反复出现的数字是 255对应的十六进制是FF。为什么是 255不是 100也不是 1000这要从早期显示器的硬件限制说起。计算机用二进制存储数据一个字节正好是 8 位能表示 0-255 共 256 个值。当时的显卡和显示器为了在性能和色彩之间取得平衡就约定每个颜色通道用一个字节来表示。这个约定一直沿用到今天成了整个 Web 色彩体系的地基。所以 RGB 的每个通道 0-255不是拍脑袋定的是硬件演化的结果。同理十六进制里FF之所以代表 255是因为一个字节 8 位拆成两个 4 位小组每组的最大值恰好是 15即十六进制的 F两组拼起来就是FF。理解了这层关系你就明白为什么#FFFFFF是白色——三个通道全部拉满叠加出来的就是最亮的光。#000000是黑色所有通道都关闭没有光。2.3 快速心算技巧整数值与十六进制的对应关系工作中我发现自己会反复用到几个基准值因为它们在十六进制和十进制之间的转换很规整就像颜色世界里的路标十进制十六进制含义255FF通道最大值亮度拉满240F0很高的亮度16×15224E0高亮度16×14208D0偏高亮度192C0明显的中高亮度160A0中等偏亮12880中间亮度也就是 50%9660中等偏暗6440明显偏暗3220很暗000通道最小值记住80是 50% 亮度这一条特别有用。比如#808080是中性灰这个灰色在任何设计里都能当安全灰用。还有#FF8000R 通道拉满G 通道 50%B 通道关闭出来的就是标准的橙色。很多东西看多了自然就记住了不用刻意背。但知道这些基准值你在调整颜色时就能省掉来回开取色器的时间。3. 三位简写与八位扩展十六进制颜色的两种特殊形态3.1 #RGB 规则什么时候能缩写成三位你肯定见过#FFF、#000、#F00这样的写法。这不是偷懒而是 CSS 规范里的一种合法简写。规则是如果每两位十六进制数字相同就可以拆成一位来写。#FFFFFF变成#FFF#FF0000变成#F00#AABBCC变成#ABC但#A5371F不能简写因为 A5、37、1F 这三组内部不是重复数字。浏览器解析三位简写时会自动把每一位翻倍还原成六位#ABC#AABBCC。所以#3AF实际是#33AAFF而不是#30A0F0。我实测过很多次三位简写在现代浏览器里完全没问题兼容性覆盖到了 IE9 及以上。所以你在写#FFF这种高频色值的时候放心用简写这不仅是省三个字符的事——对阅读者来说#FFF比#FFFFFF更直观一眼就知道是纯白。但有一个地方我不建议用简写就是你把颜色作为变量存起来准备后续迭代调色的时候。#FFF看似简洁可一旦你想把白色调成#FEFEFE就得先把变量改回六位写法。所以团队规范里如果没有特别约定我倾向于统一用六位只有像#FFF、#000、#CCC这种显式的高频中性色才用简写。3.2 八位十六进制最前面的两位是透明度CSS Color Module Level 4 给十六进制颜色增加了一个能力把透明度塞进色值里。八位格式写作#RRGGBBAA前六位照旧是 RGB最后两位表示 alpha 通道值越大越不透明FF是完全不透明00是完全透明。这里有个很容易犯迷糊的换算alpha 的80不等于透明度 80%而是等于 128/255 ≈ 50.2%。因为透明度通道也是一个字节256 个等级。举个具体例子#FF000080表示红色大约 50% 透明。它的效果等同于rgba(255, 0, 0, 0.5)——注意rgba 的 alpha 是 0 到 1 的小数而十六进制八位色值里 alpha 是 0 到 255 的整数两者表现形式不同表达的是同一个东西。我在实际项目里的经验是八位十六进制颜色读写起来都不如 rgba 直观。rgba(255, 0, 0, 0.5)一眼就能看出是半透明红而#FF000080要心里默默换算一下。所以我在团队里定了条规矩——静态的、不需要动态修改透明度的颜色用十六进制六位一旦涉及透明度优先用 rgba。不过八位十六进制有一个独特优势它能被直接从设计稿复制出来。Figma 里随便点一个带透明度的图层复制出来的 CSS 就是#RGBA格式。所以它作为设计稿到代码的中间格式很好用但不建议作为代码里的长期表达方式。3.3 大小写与格式统一问题十六进制颜色的大小写不影响解析#ffffff和#FFFFFF在浏览器里等价。但从可维护性的角度我强烈建议在一个项目里统一成一种写法。这里没有绝对的对错React Native 社区的惯例是小写Android 开发里偏爱大写而 CSS 生态里两种都很常见。我的个人习惯是全部小写 六位理由是小写字母在代码里视觉上更轻占的心理空间更小而且 CSS 变量很多工具链会自动转小写统一小写能减少 diff 噪音。如果你的项目里已经有大量大写色值那就全部用大写别混着来。混用才是最大的维护灾难。4. 从十六进制到屏幕显示浏览器到底在做什么4.1 sRGB 色彩空间与 Gamma 校正你写的#FF6347并不是直接作为亮度信号发给显示器的。这里藏着一个很多前端不知道的分层CSS 颜色值在 sRGB 色彩空间里定义而 sRGB 编码本身带有非线性特征。用人话说就是数值的 50%比如#808080并不对应人眼感知的50% 亮度。因为显示器的发光特性和人眼对亮度的感知都是非线性的sRGB 标准故意在编码时做了一个近似 gamma 曲线的映射让有限的 8 位数值能更高效地表达人眼敏感的暗部层次。这个知识点对你写 CSS 的直接帮助是你不能把颜色当成数学做线性加减。比如你觉得#808080灰太浅想加深一点直接除以二得到#404040感知上会跳得非常多而不是均匀地变暗。调色的时候要有一个感知非线性的心里预设否则很容易调出要么太亮要么太暗的极端色值。4.2 显示器差异同一段色值在不同设备上为什么不一样这里有个老生常谈但真的会踩坑的问题CSS 十六进制颜色在代码层面是确定的但最终怎么显示取决于设备的色域、色温和驱动配置。同一个#FF7F50在一台 100% sRGB 色域的显示器上看是珊瑚橙在一台广色域DCI-P3的显示器上看可能偏红在手机 OLED 屏上又普遍会更艳一些。这不是颜色代码错了是物理设备的能力不同。作为前端我做三件事来减少这种偏差设计团队统一在同一个显示器配置下评审颜色通常是标准 sRGB 下图稿。抓取设计稿色值时确认用的是 sRGB 配置而不是显示器 profile 转换过的值。涉及品牌色这种敏感颜色上线前用真机实测不能只看自己电脑的屏幕。4.3 颜色到底是怎么混出来的虽然很多前端不需要做混色计算但理解 RGB 减法混色原理能帮你在没有取色器的情况下估算颜色。举例来说你要一个略带紫调的灰。可以先想#808080是纯灰然后往里面掺一点蓝色信息。微量加法可以是在 B 通道上增加 20 左右的差值比如 B 从 128 改成 148。换算成十六进制148 ÷ 16 9 余 4B 通道就是94。所以 带紫调的灰 可以写成#808094。这种手搓颜色的方式不追求精细但非常锻炼色值直觉。我甚至建议前端新手每周花十分钟随机写三个六位十六进制色值再打开控制台验证自己判断的 RGB 准不准。练习一两个月对颜色的敏感度会明显提升。5. 实战对比与选择什么时候用 HEX什么时候用其他格式5.1 四种颜色格式的优劣对照CSS 里除了十六进制还有几种常见的颜色表达方式。我不认为哪种格式是万能的但每种有自己的长短格式示例优点缺点典型场景HEX#FF6347紧凑、设计稿直接给可读性一般不支持带括号函数静态色值、CSS 变量RGBrgb(255, 99, 71)直观表达三个通道数值稍长需要通道级运算时RGBArgba(255, 99, 71, 0.5)透明度直观更长半透明背景、遮罩层HSLhsl(9, 100%, 64%)色相/饱和度/亮度直观对新手有点门槛调色板、主题色衍生命名色tomato简单值不可控、数量有限快速原型5.2 设计稿里的色值为什么总是不成套你有没有发现设计师给的色值经常东一个西一个——#2C3E50用于标题#34495E用于正文#1ABC9C用于按钮高亮。这些颜色单看都挺协调但你想给页面加一个次级按钮的时候从哪里取色这时候我强烈建议你换个思路别直接从设计稿里抄新色值而是把设计稿色值全部整理成 HSL 模式在色相、饱和度、亮度三个维度上做衍生。执行方式很简单拿起浏览器控制台把设计稿中的主色#1ABC9C转成 HSL可以直接在 DevTools 里切换颜色格式显示得到一个类似hsl(168, 76%, 53%)的值。然后你想得到它的浅色版本就把亮度拉高比如hsl(168, 76%, 70%)想要暗色版本就把亮度压低hsl(168, 76%, 35%)想要一个不那么艳的辅助色把饱和度降到 40% 左右。由十六进制转到 HSL 做调整再转回十六进制落地这是我在实际项目里最常用的调色工作流。它的好处是你能在保持色相一致的前提下系统性地生成一套完整的颜色系统而不是靠肉眼来回试。5.3 主题化项目把 HEX 色值拆成语义变量十六进制色值本身是死的但在大型项目里你不可能把颜色直接硬编码到每个组件的每个属性里。那样改一次主题色就是一次全项目的地毯式搜索。我用十六进制色值时几乎都会配合 CSS 自定义属性和语义化命名来做。典型做法是两层结构第一层原始色板颜色令牌存的是纯粹的十六进制值:root { --color-primary-500: #1a73e8; --color-primary-600: #1765cc; --color-neutral-100: #f8f9fa; --color-neutral-900: #202124; }第二层语义变量描述颜色在组件里扮演的角色:root { --color-text-primary: var(--color-neutral-900); --color-bg-accent: var(--color-primary-500); --color-border-input: var(--color-neutral-300); }这样做的价值是产品想改品牌主色你只需要动--color-primary-500这一行所有引用了--color-bg-accent的组件自动跟随。十六进制色值在这里起到的是基础原料的作用真正在代码里到处跑的是语义变量。6. 避开这几个坑十六进制颜色最典型的四个翻车现场6.1 对比度翻车看起来好看但看不清我见过太多的页面按钮用了#B3B3B3的底、#FFFFFF的字设计稿里看高级感拉满真机上老人根本看不清。这就是典型的对比度不足问题。WCAG 2.1 的 AA 级别要求普通文本的对比度不低于 4.5:1大号文本不低于 3:1。作为前端不需要死记这个公式但要养成用工具检查的习惯。DevTools 自带的颜色选择器会在你点击色值的时候直接显示当前前景色与背景色的对比度并标出是否通过 AA/AAA。浏览器控制台同样能看到。我在写任何需要文字可读性的界面时都会用#RRGGBB色值配合对比度检查器过一遍绝不靠肉眼判断。6.2 透明度被忽略alpha 通道引发的叠色意外八位十六进制色值#00000080和六位#000000在纯白背景下看起来效果完全不同但代码里只多了两个字符。这种 bug 很难发现因为你一眼扫过去注意力已经被前六位黑色吸引走了。我的建议是只要写八位十六进制色值旁边一定加注释标明它等价于什么样的 rgba 表达式。比如background-color: #00000080; /* rgba(0, 0, 0, 0.5) */这样既保留了十六进制从设计稿直出的便利又让团队其他人一眼看懂透明度是多少。6.3 色值之间的几乎一样肉眼找不到的重复两个颜色#F5F5F5和#F6F6F6肉眼基本分不出来但它们就是两个不同的色值。如果代码里同时出现了好几个几乎一样的灰后期维护时你想统一成设计系统里的灰就得费很大劲。解决这个问题有两个工具用 CSS 预处理器Sass/Less的map-get或者 CSS 变量统一管理色板从源头杜绝随手写近似色。代码审查时专门检查新增的十六进制色值发现不在色板里的一律让开发者从设计系统里选或者申请新增色板规范。6.4 深色模式十六进制硬编码的反噬如果你把大量十六进制色值直接写在组件样式里等产品要求做深色模式时你就得把每个组件翻出来改。这个工作量极其痛苦。我的经验是从第一天起就把颜色抽象成语义变量而不是直接写 HEX。深色模式切换时只需要在根元素上换一套底层色板变量的值所有组件自动适配:root[data-themelight] { --color-bg-primary: #ffffff; --color-text-primary: #1a1a1a; } :root[data-themedark] { --color-bg-primary: #1a1a1a; --color-text-primary: #f2f2f2; }十六进制色值仍然是你定义色板的原料但不要让它泄漏到组件代码里。这个习惯越早养成后面越省心。7. 一些让十六进制颜色更好用的细节7.1 从设计稿正确取色很多前端从设计稿取色时直接点一下 Photoshop 或者 Figma 的吸管复制出来的不一定是 sRGB 十六进制值。如果你的设计软件开了显示器 P3 色域预览之类的配置复制出来的色值可能包含超出 sRGB 范围的色域。这个色值写进 CSS在支持 P3 的屏幕上会变样。我建议在 Figma 偏好设置里确认颜色配置为sRGB。取色后顺手在浏览器里打开一个纯色页面对比一下色差超过肉眼感知阈值就说明配置有问题。7.2 浏览器工具里的颜色格式切换Chrome DevTools 的 Styles 面板里按住键盘Shift并点击色块会在HEX、RGB、HSL、HWB等格式之间循环切换。这个功能我在日常调试时用得极其频繁。比如我看到一个颜色#1a73e8想把它调得更冷一点点击切到hsl(217, 89%, 53%)直接把色相手动从 217 调到 230再切回十六进制一个全新的蓝色就出来了。整个过程不用开任何外部工具效率极高。7.3 CSS 变量里存十六进制还是存原始通道值这是一个很细的实践问题。我见过两种做法存完整色值--brand-color: #1a73e8;存通道值--brand-r: 26; --brand-g: 115; --brand-b: 232;前者简单直接但如果你想用这个颜色再包一层半透明就写不了。后者需要配合rgba(var(--brand-r), var(--brand-g), var(--brand-b), 0.5)写法笨重但能灵活生成任意透明度的派生色。我的折中方案是普通项目用完整色值如果明确知道这个色值要频繁派生不同透明度比如品牌色用于不同力度的阴影、遮罩、hover 反馈就拆成通道变量。工程上很少有标准答案看场景做取舍而已。7.4 让取色器工具和你的工作流配合说实话现在的浏览器开发者工具已经很强大了但天然有一个盲区你不能在 DevTools 里全局搜索哪个文件用了这个颜色。因此在大型项目里重构颜色时我通常会借助 IDE 的全局搜索配合正则匹配十六进制色值#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b。一条正则扫下去项目里所有十六进制色值都列出来再逐个归类到语义变量里。不过这个操作只适合初始重构的场景。如果项目还在持续迭代我更推荐每次改动时顺手把接触到的硬编码色值替换成语义变量用小步快跑的方式渐进式收敛而不是憋一个大重构再动刀。大重构期间功能迭代不能停冲突会把你搞疯。8. 我对十六进制颜色的最终态度写到这里说点实际的个人体会。我见过很多前端工具用得很熟#FF6347复制到浏览器里也显示得很准但他们并不真正理解这个色值在表达什么。遇到需要调色、派生新色、排查显示差异的时候就只能靠猜、靠试或者反复找设计确认——一天下来效率全耗在这种应该一秒解决的小事上。而当你理解了十六进制颜色等于 RGB 通道的十六进制编码理解了三位简写的展开规则、八位色值里的 alpha 通道、sRGB 的非线性特征你在处理任何颜色相关的问题时心里都会有一个清晰的坐标系这个色值到底在描述哪个通道的高低我如果要调整它应该动哪一位它对最终显示的影响是什么。这也是我在团队里带新人时反复强调的从能显示正确到能预测显示效果中间隔着的就是对基础编码机制的理解。十六进制颜色看起来是最不起眼的 CSS 知识点之一但它恰恰是联结设计语言、浏览器渲染和物理显示设备的第一道桥梁。另外如果你发现自己老是记不住十六进制和 RGB 的对应关系别心虚这不代表你能力不行。我自己也是用了三四年才慢慢把常见色值区间刻进直觉里的。工具是拿来辅助效率的而原理是拿来兜底的——两样配齐你在颜色这件事上就不会再翻车。