做前端这些年被问得最多的问题之一就是这个绿到底是哪个色号设计稿截图发过来旁边标着“浅一点的灰”产品文档里写着“用品牌蓝”老项目里#F5F5F5和#FAFAFA混着用谁也说不清谁是谁。网页颜色代码对照这件事看起来只是背几个十六进制值实际踩进去才知道坑有多密三位简写少写一位、透明色写成transparent导致渐变发灰、暗色模式下文字糊成一片、打印出来和屏幕上完全两个颜色。我手上这份对照表前后改过五六版最初就是从网上转来的越用越发现错漏不少索性按自己的项目习惯重排了一遍。这篇就把网页颜色代码对照的底层规则、表格设计、落地到 CSS 变量的步骤、对比度校验和常见翻车现场一次讲透。刚入行的朋友可以当成工具手册有经验的朋友可以直接抄走表格结构和脚本。1. 先把三套记法之间的换算关系捋顺1.1 十六进制为什么是六位三位简写到底省了什么十六进制的每一位取值范围是 0 到 15用 0-9 和 A-F 表示。一个颜色通道占两位正好覆盖 0 到 255三个通道拼起来就是六位。拿#3A7BD5来说拆开是3A、7B、D5换算成十进制分别是 58、123、213写成rgb(58, 123, 213)。手算的方法很简单高位乘 16 加低位。3A就是 3×161058D5就是 13×165213。记住 A10、B11、C12、D13、E14、F15剩下就是小学算术。三位简写的规则只有一条每一位必须能复制成两位。#ABC等价于#AABBCC#FFF等价于#FFFFFF。#AABBCD这种就不能简写因为第三组CD两个字符不一样。很多人以为三位简写是“省略后三位”这是错的。还有一个高频错误是写成五位#FFFFF浏览器会直接把整条声明丢掉颜色回退到继承值或者默认值排查时很容易误判成选择器优先级问题。四位写法#RGBA是另一回事#0000表示全透明的黑不是#000000的简写这一点在写遮罩层的时候特别容易搞混。提示看到颜色不生效先数一下井号后面是不是六位或八位再看有没有混进非十六进制字符。这个检查花不了十秒钟能省掉半小时的瞎找。1.2 RGB 与 HSL 的换算逻辑调色时该用哪个RGB 的三个数字直接对应屏幕的三个发光通道取值范围 0 到 255百分比写法则是 0% 到 100%。它的优点是直白缺点是“不直观”——你想把蓝色调亮一点得同时改三个数改完还不一定对。HSL 把颜色拆成色相、饱和度、亮度三个维度色相是 0 到 360 的角度饱和度是灰到艳的程度亮度是黑到白的程度调色时改一个维度就行心智负担小很多。手算 HSL 到 RGB 的公式并不复杂。以hsl(210, 70%, 50%)为例先算色度 C(1-|2L-1|)×SL 取 0.5S 取 0.7得到 C0.7。色相 210 除以 60 得 3.5落在第三区间对应的中间值 XC×(1-|H mod 2 -1|)0.7×0.50.35。偏移量 mL-C/20.15。第三区间的三元组是 (0, X, C)加上 m 得到 (0.15, 0.5, 0.85)乘以 255 就是 (38, 128, 217)十六进制写作#2680D9。整个过程手算两分钟写脚本的话一秒钟。这里有个必须提前知道的认知HSL 里的 L 是数学亮度不是人眼感知亮度。同样 L50% 的黄色和蓝色黄色看起来亮得多。这就解释了为什么有些颜色明明白白写着“中等亮度”放到页面上却刺眼或者发闷。真正判断可读性要看相对亮度那是另一套公式后面第 4 节会细说。我的习惯是调色用 HSL校验用相对亮度两套东西各干各的活。1.3 命名颜色和特殊关键字别被“白色”骗了CSS 有一百多个命名颜色white是#FFFFFF但snow、ivory、floralwhite也都接近白色肉眼几乎分不出来。命名颜色的好处是可读坏处是容易记混而且不同命名之间的细微差别在暗色模式或者低亮度屏幕上基本看不出来。我的建议是对照表里保留命名颜色作为速查但项目代码里一律用十六进制或变量避免“这个gainsboro到底偏冷还是偏暖”这种无意义的争论。特殊关键字里transparent等价于rgba(0,0,0,0)它是透明黑不是透明白。做渐变淡出时如果写linear-gradient(to bottom, #FF0000, transparent)中间会经过一段灰扑扑的过渡因为颜色在往黑色方向插值。正确做法是写rgba(255,0,0,0)让 RGB 三个通道保持一致只降透明度。currentColor是另一个好东西它会继承当前元素的color值做图标、边框、下划线时特别省事改文字颜色的时候装饰元素自动跟着变。至于Canvas、ButtonText这类系统色会随用户主题变化做跨平台界面时尽量别用不然测试环境和你本机看到的完全是两回事。2. 一张真正能用的对照表长什么样2.1 按用途分层比按字母排序有用十倍网上转来的对照表大多是按颜色名首字母排的查起来像翻字典查到之后还得自己判断该用在哪。我的做法是按用途分四层中性色、品牌主色、语义色、功能色。中性色管背景、边框、次要文字品牌主色管按钮和链接语义色管成功、警告、错误、信息四种状态功能色管遮罩、阴影、骨架屏这类零碎场景。每一层内部再按 50 到 900 的色阶排数字越小越浅越大越深中间 500 通常是基准色。这种排法的好处是替换成本低。品牌色要改只动primary那一列暗色模式要调把色阶整体反查一遍就行不用逐个颜色去想“这个灰该配哪个灰”。下面这张表是我现在项目里在用的结构字段包括色阶、十六进制、RGB、HSL 和典型用途实际使用时可以按需增删。色阶HEXRGBHSL典型用途gray-50#F8FAFC248,250,252210,40%,98%页面底色、卡片底gray-100#F1F5F9241,245,249210,40%,96%分区背景、表头gray-200#E2E8F0226,232,240214,32%,91%分割线、输入框边框gray-300#CBD5E1203,213,225213,27%,84%禁用态边框gray-400#94A3B8148,163,184215,20%,65%占位文字、图标gray-500#64748B100,116,139215,19%,47%次要文字gray-600#47556971,85,105215,19%,35%正文辅助gray-700#33415551,65,85215,25%,27%正文gray-800#1E293B30,41,59217,33%,17%标题gray-900#0F172A15,23,42222,47%,11%主文字、深色底2.2 每个颜色至少记四个字段少一个都会在后面补课只记十六进制是不够的。RGB 字段在接图表库、Canvas 绘制、做透明叠加计算时要用HSL 字段在调色和生成色阶时要用用途字段则是给团队看的防止新人把gray-200拿去当正文颜色。我还在表里加了一列对比度标注这个颜色放在白底上的对比度值低于 4.5:1 的直接标红提醒只能用于装饰不能用于正文。这一列在评审设计稿的时候特别好使不用每次都打开计算器。对比度这一列的计算不复杂但手工算很烦建议用脚本批量跑一遍。表格里的数值一旦定下来就同步写进项目文档别只存在某个人的备忘录里。人员一换颜色就乱这是小团队最常见的返工原因。2.3 透明度三种写法用错了会牵连子元素透明度至少有三种写法rgba(0,0,0,0.1)、八位十六进制#0000001A、以及opacity: 0.1。前两个只影响颜色本身第三个会影响整个元素包括所有子元素。做半透明遮罩时用前两个做整体淡入淡出时才用opacity。八位十六进制的后两位是 alpha 通道换算方法是把 0 到 1 的小数乘以 255 再取整转十六进制0.1×25525.5四舍五入 26即1A0.5×255127.5取 128即800.8×255204即CC。写法示例影响范围适用场景rgbargba(0,0,0,0.1)仅该颜色遮罩、边框、阴影八位 HEX#0000001A仅该颜色同上适合变量存储opacityopacity: 0.1整个元素及子元素整体淡入淡出、禁用态注意父元素设了opacity子元素再怎么设opacity: 1也救不回来因为父元素的透明度会作用在合成后的图层上。这个坑在弹窗遮罩加内容卡片的结构里特别常见。3. 把对照表落进项目CSS 变量与色阶生成3.1 变量分两层原始色板和语义映射各管一摊直接把#3B82F6写进按钮背景短期没问题长期一定乱。我的做法是分两层第一层是原始色板只描述颜色本身比如--blue-500第二层是语义变量描述用途比如--color-primary、--color-text-secondary。组件里只引用第二层换品牌色的时候改第一层的映射就行组件代码一行不动。这种结构在只有两三个页面的时候显得啰嗦页面一多就知道值了。:root { /* 第一层原始色板 */ --gray-0: #FFFFFF; --gray-50: #F8FAFC; --gray-100: #F1F5F9; --gray-500: #64748B; --gray-900: #0F172A; --blue-500: #3B82F6; --blue-600: #2563EB; /* 第二层语义映射 */ --color-bg-page: var(--gray-50); --color-bg-card: var(--gray-0); --color-text-primary: var(--gray-900); --color-text-secondary: var(--gray-500); --color-border: var(--gray-100); --color-primary: var(--blue-500); --color-primary-hover: var(--blue-600); }命名上有个小建议语义变量别带颜色词。叫--color-danger比叫--color-red好因为以后品牌色换成橙色变量名不用改。原始色板则可以带颜色词因为它描述的就是颜色本身。两层各司其职别混着用。3.2 暗色模式不是把颜色反过来亮度要重新配很多人做暗色模式的第一反应是白底黑字换成黑底白字结果页面上出现大面积纯白文字看五分钟眼睛就累。纯黑背景配纯白文字的对比度是 21:1远超需要反而造成光晕和拖影。实际做法是背景用#121212到#1A1A1A之间的深灰正文用#E0E0E0左右标题再用亮一档整体对比度控制在 12:1 到 15:1 之间。阴影在暗色下几乎看不见分层要靠边框和背景亮度差这是两套完全不同的视觉逻辑。media (prefers-color-scheme: dark) { :root { --color-bg-page: #121212; --color-bg-card: #1E1E1E; --color-text-primary: #E6E6E6; --color-text-secondary: #A0A0A0; --color-border: #2C2C2C; --color-primary: #60A5FA; } }如果项目需要手动切换主题就用属性选择器:root[data-themedark]把上面这套变量再写一遍。两套都写的时候注意顺序属性选择器的优先级高于媒体查询里的:root别互相覆盖出意外。还有一行容易被忽略的color-scheme: light dark;它会影响浏览器原生滚动条、输入框、下拉菜单的配色加上之后暗色模式才完整。3.3 用脚本批量生成色阶但最后一定要肉眼校手工调十个色阶很费时间用脚本生成初稿再微调效率高得多。思路是固定色相和饱和度按一组亮度值批量输出十六进制。下面这段脚本用 Python 写输入色相、饱和度输出 50 到 900 的色阶亮度序列可以按自己的喜好调整。import colorsys def hsl_to_hex(h, s, l): r, g, b colorsys.hls_to_rgb(h / 360, l / 100, s / 100) return #{:02X}{:02X}{:02X}.format( round(r * 255), round(g * 255), round(b * 255) ) steps [97, 94, 86, 76, 66, 56, 46, 36, 26, 16] for index, lightness in enumerate(steps): level 50 if index 0 else index * 100 print(f--primary-{level}: {hsl_to_hex(215, 85, lightness)};)跑出来的结果直接贴进 CSS 就能用。但别迷信脚本HSL 的亮度是数学值黄色和蓝色在同一个 L 值下观感差很多。我的习惯是脚本出稿之后把十个色块并排放在页面上看一眼偏亮或偏暗的手动挪一两档。色阶的作用是提供稳定的选择范围不是追求数学上的完美等距。4. 对照表之外对比度才是可读性的底线4.1 相对亮度怎么算4.5:1 到底卡的是什么对比度的公式是 (L10.05)/(L20.05)L 是相对亮度。相对亮度不能直接拿 RGB 的平均值要先把每个通道除以 255再做伽马校正小于等于 0.03928 的除以 12.92大于的按 ((c0.055)/1.055) 的 2.4 次方处理。然后按 0.2126、0.7152、0.0722 的权重加权求和。这套公式看起来吓人写成代码就几行。def luminance(hex_color): hex_color hex_color.lstrip(#) rgb [int(hex_color[i:i2], 16) / 255 for i in (0, 2, 4)] linear [c / 12.92 if c 0.03928 else ((c 0.055) / 1.055) ** 2.4 for c in rgb] return 0.2126 * linear[0] 0.7152 * linear[1] 0.0722 * linear[2] def contrast(fg, bg): l1, l2 luminance(fg), luminance(bg) lighter, darker max(l1, l2), min(l1, l2) return (lighter 0.05) / (darker 0.05) print(round(contrast(#767676, #FFFFFF), 2)) # 4.54 print(round(contrast(#999999, #FFFFFF), 2)) # 2.85#767676放在白底上的对比度是 4.54:1刚好过正文的 4.5:1 门槛这就是为什么很多设计系统把次要文字定在这个灰度附近。#999999只有 2.85:1做装饰线没问题做正文就偏淡。大字号18pt 常规或 14pt 粗体以上的门槛是 3:1图标和表单边框这类非文本元素也是 3:1。这几个数字记牢评审的时候不用现查。4.2 状态色不能只靠颜色区分色觉差异要考虑成功用绿、错误用红、警告用黄这套约定俗成没问题但如果只靠颜色区分红绿色觉差异的用户就可能分不清。实际做法是颜色加图标加文字三重保险成功配对勾错误配叉号警告配感叹号文字说明写清楚。颜色本身也可以调纯红#FF0000和纯绿#00FF00放在一起既刺眼又难辨把饱和度降下来、亮度拉开差距辨识度反而更高。状态推荐色白底对比度配套元素成功#15803D5.02:1对勾图标 文字警告#B453095.02:1感叹号图标 文字错误#B91C1C6.47:1叉号图标 文字信息#1D4ED86.70:1信息图标 文字表格里的对比度是按白底算的放到浅灰底或者暗色底上要重新算一遍。我见过不少项目直接把这几个色值搬到暗色模式结果深红配深灰几乎看不见。暗色下的状态色要整体提亮比如错误色从#B91C1C换到#F87171对比度才能回到可用范围。5. 取色、转换、验证的完整操作流程5.1 从设计稿取色别对着压缩截图吸取色最稳的来源是设计源文件和 PNG 导出图JPG 截图有压缩噪点吸出来的颜色经常是#F7F8F9这种“差一点”的值。截图被缩放过的更麻烦边缘会产生插值吸到的是混合色。浏览器开发者工具里的取色器读的是渲染后的像素值比较可靠但要注意页面有没有加滤镜、混合模式或者半透明遮罩这些都会改变最终呈现的颜色。夜间模式、护眼模式这类系统级色温调节只影响肉眼观感不影响取色器读到的数值不用太担心。取到颜色之后先看它“整不整”。设计稿里的颜色通常是#1890FF、#F5F5F5这种规整值如果你吸出来是#1A8BF2大概率是截图压缩或者缩放导致的偏差回头找设计源文件确认一下更稳妥。项目里最怕的就是一堆“差一点”的颜色看着差不多实际有七八个近似值维护起来非常痛苦。5.2 批量转换脚本先备份再动手老项目里 RGB 和十六进制混用是常态统一格式用脚本比手工改靠谱。下面这段逻辑可以扫描样式文件里的rgb(...)写法并替换成十六进制支持三位参数不带透明度的版本。import re def rgb_to_hex(match): r, g, b (int(x) for x in match.groups()) return #{:02X}{:02X}{:02X}.format(r, g, b) pattern re.compile(rrgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)) text pattern.sub(rgb_to_hex, text)跑之前一定先提交代码或者备份文件先拿一个文件试跑确认输出没问题再批量处理。带透明度的rgba不要一起转除非你确认目标环境支持八位十六进制否则转完反而出问题。转换完还要抽查几个颜色在页面上的实际效果脚本只保证格式正确不保证视觉效果没变。5.3 上线前的颜色检查清单颜色这种东西开发环境看着没问题上线之后被用户截图投诉的例子太多了。我一般在上线前过一遍这几项正文对比度是否都过 4.5:1暗色模式下有没有哪个区块文字糊掉高对比度模式系统辅助功能下边框和图标还在不在打印预览里深色背景有没有变成一片黑表单的禁用态、悬停态、聚焦态是不是都能区分。这几项加起来花不了二十分钟能挡掉大部分视觉回归问题。检查项合格标准常见问题正文对比度≥ 4.5:1浅灰文字放白底大字与图标≥ 3:1图标颜色太淡暗色模式无纯白大面积文字直接反色导致刺眼状态区分颜色 图标 文字只靠红绿区分打印样式深底改浅底背景色不打印6. 常见问题与排查速查表颜色问题的排查有个特点现象相似原因五花八门。下面这张表是我这些年攒下来的高频问题按现象、原因、处理方式三列整理遇到问题时从上往下对一遍大部分情况能定位到。现象可能原因处理方式颜色完全不生效十六进制位数不对如五位数位数确认六位或八位颜色被覆盖选择器优先级、内联样式、!important在开发者工具里看计算后的样式来源变量不生效变量未定义、拼写错误、作用域不对检查:root定义和var()拼写渐变中间发灰用了transparent做透明端改成同色rgba(...,0)子元素跟着变透明父元素用了opacity改用rgba或八位十六进制暗色模式没切换变量被更高优先级覆盖检查属性选择器和媒体查询顺序移动端颜色偏艳广色域屏幕色彩映射用标准色域值避免超出范围的艳色打印颜色发暗屏幕色与印刷色差异单独写打印样式深底改浅底命名颜色不识别拼写错误或用了非标准名改用十六进制别赌浏览器容错半透明叠加后偏色多层透明叠加计算复杂合并为一个不透明色或减少层数表格之外还有几个值得单独说的。一个是“颜色看着一样但值不一样”项目里同时存在#FFFFFF、#FEFEFE、#FDFDFD肉眼几乎分不出但代码里就是三个颜色。这种问题只能靠统一变量来解决发现一个改一个别指望一次性清理干净。另一个是浏览器默认样式带来的干扰比如输入框的自动填充背景、按钮的默认边框这些颜色不受你的变量控制需要单独覆盖。还有就是图片上的文字背景图颜色不可控文字必须加半透明遮罩或者描边不能赌图片某个区域刚好够暗。提示排查颜色问题时先看计算后的样式再看变量定义最后看有没有滤镜和混合模式。顺序反了容易在错误的方向上耗时间。7. 我在实际项目里攒下的几条经验第一条先定中性色再定品牌色。中性色占了页面面积的八成以上灰阶没定好品牌色再漂亮也撑不起来。很多设计稿一上来就纠结主色是蓝一点还是紫一点结果背景灰和文字灰有十几个近似值整个页面看着脏。我的习惯是先把 gray 的十个色阶定死再往里塞品牌色冲突会少很多。第二条色板不要贪多。一个项目里超过三十个颜色值基本就失控了。品牌色五个色阶够用中性色十个语义色四个加起来二十个左右剩下的用透明度和混合凑。色阶越多选择越多越容易出现在两个近似色之间反复横跳的情况。第三条变量命名别用颜色词做语义。--color-red这种名字以后品牌色换成橙色就尴尬了改吧要动所有引用不改吧名字和实际颜色对不上。语义命名--color-danger从第一天就该用起来前期多打几个字后期少改一堆文件。第四条对照表要进版本库别放在个人笔记里。颜色是团队协作的公共资产谁都能查、谁都能改、改了有记录比口口相传靠谱得多。表格里除了色值把用途和对比度也写上新人接手的时候不用一个个问。第五条别过度依赖在线取色工具。工具给的是一串数字能不能用还得看场景。同一个灰色放在白底上合适放在浅灰卡片上可能就不够了同一个蓝色做按钮好看做正文链接可能对比度不够。颜色没有绝对的好坏只有配不配当前场景。最后分享一个我常用的小技巧把项目的所有颜色变量导出来生成一张色卡页面每个色块下面标注变量名、色值和对比度。这张页面不用上线本地打开看一眼哪个颜色重复了、哪个对比度不够、暗色模式下哪个色没配对一目了然。比起在几十个文件里翻变量这张色卡省下来的时间相当可观。
阅读完成 · 觉得有帮助?