这个正则匹配什么
覆盖所有 CSS 十六进制颜色写法的正则:
^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
从左到右读:
^#要求必须有井号。少了它,你匹配的根本不是 CSS 颜色。- 非捕获组给出三选一,按顺序尝试:
[0-9a-fA-F]{3,4}覆盖三位#RGB和四位#RGBA简写。[0-9a-fA-F]{6}是经典的六位#RRGGBB。[0-9a-fA-F]{8}是带透明度的八位#RRGGBBAA。
$锚定结尾,尾部多余字符一律拒绝。
为什么 3、4、6、8 位都合法
CSS 定了一条简写规则:在三、四位写法里,每个十六进制数字会被复制一次凑成完整值。于是:
| 形式 | 例子 | 展开成 |
|---|---|---|
#RGB | #f0a | #ff00aa |
#RGBA | #f0a8 | #ff00aa88 |
#RRGGBB | #ff00aa | 自身 |
#RRGGBBAA | #ff00aa88 | 自身 |
这就是为什么正则必须同时接受 3 和 4(不能只接受 3)、以及 6 和 8(不能只接受 6)。把位数写死成 {6} 的正则会悄悄把 #fff 判错,而那恰恰是网页上最常见的颜色之一。
忽略大小写的写法
十六进制数字不区分大小写:#FFF 等于 #fff。你可以把字符类写成 [0-9a-fA-F],也可以写成 [0-9a-f] 并在编译时打开忽略大小写标志。两者匹配同样的字符串。选语言里更干净那种就行,但别两种都忘,否则 #ABCDEF 会被拒。
至于要不要允许没有 #,这是个产品决策。在严格的 CSS 语境里井号是强制的;在用户输入框里你可以接受 1a2b3c 然后自己补上井号。上面的主正则要求井号,变体里给了可省略版。
正则判断不了这个颜色能不能渲染
这是核心,值得说直白点:正则只能告诉你一个字符串"长得像"十六进制颜色,却告诉不了你它是不是合法 CSS、会不会真的被应用、在上下文里有没有意义。
在浏览器里,你手里本就有一个好得多的校验器,那就是 CSS 引擎:
function isColor(value) {
return CSS.supports('color', value);
}
CSS.supports 用的是和浏览器一模一样的解析器,所以它能接受 #fff、rebeccapurple、rgb(0 0 0 / 50%),也能用真正的解析逻辑拒掉胡说八道。它还帮你面向未来:新颜色语法一出来,正则会坏,CSS.supports 不会。
如果你不在浏览器里,那就自己解析,或者依赖一个体积小、经过充分测试的颜色库,而不是手搓正则。正则适合回答"这段日志里有没有像颜色的东西",不适合回答"这到底是不是个真颜色"。
常见错误写法
| 错误写法 | 问题 | 正确写法 |
|---|---|---|
^#[0-9a-f]{6}$ | 把 #fff、#ffff、#ffffffff 全拒了。 | 把 3、4、6、8 四种都收进来。 |
^[0-9a-f]{6}$ | 忘了强制的 #。 | 要求 #,或 deliberate 地决定不要。 |
^#[0-9a-fA-F]{8}$ | 漏掉了常见的三、六位写法。 | 四种长度都要。 |
把 #ffff 和 #ffffff 搞混 | 四位是 RGBA,不是写坏的六位。 | 四位是 RGBA,八位是 RRGGBBAA。 |
#fff 与 #ffff 的区别
一个高频困惑点:#fff 是三位(简写 RGB)。#ffff 是四位,也就是说 #ffff 是带满透明度的简写 RGBA,并不是写坏的六位颜色。同理,#ffffff 是六位 RGB,#ffffffff 是八位 RGBA。光看位数就能知道你面对的是哪种通道布局,正则里的分支也正是照此而来。
从文本里提取所有颜色
有时你要的不是校验单个值,而是从一大段文本(样式表 dumped 出来的内容、日志、用户粘贴)里把所有十六进制颜色抠出来。这时去掉锚点,开全局标志:
/#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})/g
那个 边界能阻止 #1234567 把前六位当成颜色匹配走。记住这叫"提取",不是"校验意图":一串碰巧长得像颜色的十六进制,并不自动就是页面会用到的颜色。
什么时候正则才是对的
公平地说,在很窄的场景里正则有它的位置:
- 在没有 CSS 解析器的非浏览器语言里,对一个值做快速体检。
- 从日志或粘贴文本里快速扫出像颜色那样的 token。
- 在更深层解析之前,于表单边界做一次快速拒绝。
但只要问题变成"这个颜色到底会不会被用上",就去找平台自带的解析器。正则回答的是"它看起来对不对",解析器回答的是"它到底对不对"。