使用方法
- 把 CSS 粘进左边「粘贴 CSS」。本页的 CSS格式化美化 会把它重排成缩进对齐的样子。默认已预填一段 383 字节的示例(含
@media嵌套和@supports),你直接点重排就能看到效果。 - 选缩进档位:在「重排方式」里选展开(每条声明独占一行,默认)或压成一行(每条规则压成一行,适合塞进 HTML 的
style属性)。缩进宽度在输入框下方的下拉里选 2 空格、4 空格或制表符。 - 需要按属性名字典序整理同一规则内的声明时,勾上「声明内按属性名排序」。这个选项只在「压成一行」模式下有效果——展开模式下每条声明已经各占一行,没有可排序的对象。
- 点「重排 CSS」。多行输入框里 Enter 是换行,要触发计算请按 Ctrl+Enter(Mac 是 Cmd+Enter)。
- 点「复制结果」把重排后的 CSS 带走,或点「导出 CSV」拿到 12 行「片段 → 字节数」对照表,用来看你的 CSS 到底被压缩成了什么样。
这个 CSS格式化美化 能解决什么问题
一是接手别人压过的样式表。前端项目里经过构建工具压缩的 CSS 常是「一整行几百个规则」,人眼根本没法 review。用 CSS格式化美化 展开后,每条声明独占一行、缩进对齐,grep 一个类名就能看到它全部的声明,review 效率完全不同。CSS格式化美化 只动空白和换行,所以你可以放心地把它接进任何项目的本地工作流。
二是定位「我的样式为什么没生效」。压缩过的 CSS 里如果有一条 content:"a;b" 被错误地按分号切断了,展开后你立刻能看到哪一条声明被劈成两半;同理,注释里的 /* {;} */、data URI 里的 url(data:image/svg+xml;base64,...) 也一眼看得出有没有被破坏。
三是统一团队的缩进风格。有人用 2 空格、有人用 4 空格、有人用 Tab,改一次全站对齐比在编辑器里手动调快得多。页面提供 2 空格 / 4 空格 / 制表符三个档位,选定后整份文件按同一规则重排。
四是给压缩前做体检。页面下方那张 12 行对照表逐个列出常见写法的「输入字节 → 输出字节 → 规则数 → 声明数」,你把自己的片段粘进去对照一下,就知道这份 CSS 里哪部分最占体积。
本页不做这些事:不解析 CSS 语法树(不做 AST 重写、不做语义级合并、不删冗余选择器)、不改属性值内部的内容(calc()、var()、clamp() 一律原样保留)、也不做 Lint 查错。要把 CSS 体积真正压小,请用「CSS压缩工具」;要查语法错误和浏览器兼容性,请用「CSS校验」。
原理和变量:一个状态机,四类保护上下文
先说清定位:这是逐规则文本级重排,不是 AST 解析。整个工具只有一个从左到右扫一遍的状态机,靠「花括号配对 + 分号切分 + 缩进计数」把文本重新断开。它不理解选择器优先级、层叠顺序、也不做 Shorthand 展开或重复声明合并。真正的 AST 工具(比如 PostCSS 生态)要先把 CSS 解析成节点树,代价是体积大一个数量级,且必须处理自定义属性、@supports 条件等所有边缘语法。本页刻意选文本级路线,代价是不理解语义,好处是不改一个字符的语义——只动空白和换行。
四个必须保护的地方。这是本页存在的核心理由,也是绝大多数「一键美化」出错的根源:
① 块注释 /* */。注释里出现分号和大括号是合法的,/* a;b;c */ 里的三个分号不是声明分隔符。状态机扫到 /* 就跳到匹配的 */,整段原样吐出,一个字符都不动。
② 双引号和单引号字符串。content:"a;b" 里的分号、content:"{}" 里的两个花括号都是字面量内容,不是语法。扫到引号就找配对的闭引号(中间遇 \ 跳过下一个字符),整段收下。转义引号 content:"\"" 也正确处理——反斜杠让状态机跳过紧跟的那个引号,不会误判为结束。
③ 未加引号的 url( ... )。这是最容易漏掉的一处:background:url(data:image/svg+xml;base64,PHN2Zy8+) 里的分号是 data URI 的一部分,而 CSS 语法允许 url() 内不加引号。状态机识别 url( 后一直读到配对的 ),中间无论出现什么字符都当作字面量。漏掉这一条,图标和内联 SVG 的背景就会全部被切碎。
④ 块注释之外的 // 行注释。这不是合法 CSS,所以工具不识别它——如果你在 CSS 里写了 //,页面会把它当普通字符处理并给出提示。
补分号这条规则。CSS 允许最后一条声明省略分号。重排器在遇到 } 时会检查缓冲区里剩下的内容,如果末尾没有 ; 就补上。所以 a{color:red} 会变成三行的 color:red;。这不是「多加了东西」,而是把省略的语法显式化,让每条声明长得一样。
缩进怎么算。缩进宽度 = 当前花括号深度 × 每层宽度。示例的 @media 块里 .card 的声明深度是 1,所以缩进 2 空格,.card 的选择器本身缩进 0。同一个片段换 4 空格,输出从 475 字节变成 527 字节;换成制表符则降到 449 字节——制表符最省体积,因为 1 个字节就顶 1 层缩进,而 4 空格要 4 个字节。
「压成一行」用的是栈,不是拼接。把一条规则连同它内部所有嵌套规则压成一行时,需要知道每层规则的边界,所以用了帧栈:每遇 { 压一帧({head, decls, kids, ind}),每遇 } 弹一帧、拼成一行、再 push 回父帧。不用栈就会错位——@media 里的规则会跑到 @media 外面去。缩进值随帧一起存(ind),所以收口时不受深度已递减的影响。
一个可以验证的性质:幂等性。把输出再喂回输入,结果应逐字节完全相同。示例片段跑两次都是 475 字节 / 31 行,完全一致。幂等不成立就说明重排器有状态残留 bug(比如缓冲区没清干净),这是最省事的自检方法。compact 模式同样幂等(425 字节两次一致)。
完整示例(数值仅作演示)
示例条件与页面打开时的默认值完全一致:383 字节 / 5 行的压缩片段、展开模式、2 空格缩进、不排序。逐项实测结果如下:
- 规则数(花括号对)= 8(
.card、.card::after、.card > .title各 1,@media内 2,@supports内 1,@ 规则本身也算一条规则); - 声明数(受保护上下文之外真正终止的分号 + 末尾无分号那条)= 15;
- 输入 383 字节 / 5 行 → 输出 475 字节 / 31 行,体积增加 24.0%——这是格式化必然付出的代价:美化是为了给人看,上线前仍需再压一次;
- 换 4 空格:527 字节(+52);换制表符:449 字节(−26);
- 压成一行模式:425 字节 / 5 行,勾上声明内排序后仍是 425 字节(排序不改变字节数,只改变顺序);
- 幂等性:输出再跑一次仍是 475 字节 / 31 行,逐字节相同。
结果框在页面打开时就填着上面这段展开结果(服务端直出),你点一次「重排 CSS」会得到完全一样的内容——这是用来确认状态机没坏的基准值。
输入范围与对照表
输入上限是字符数而不是字节数,因为状态机是逐字符扫描的:超过 200000 字符会先提示,超过 500000 字符直接拒绝并给出具体数字。实测一台普通笔记本上 2000 条规则的片段(81745 字节)单趟重排耗时约 10 毫秒,所以性能不是瓶颈,DOM 才是——几十万行塞进 <pre> 会让标签页卡住。
| 行 | 常见写法 | 声明数 | 规则数 | 输入字节 | 输出字节(2 空格) | 输出行数 |
|---|---|---|---|---|---|---|
| 1 | a{color:red;background:blue} 基础选择器 | 2 | 1 | 28 | 36 | 4 |
| 2 | h1,h2,h3{margin:0;font-weight:700} 多选择器组 | 2 | 1 | 34 | 42 | 4 |
| 3 | a:hover{text-decoration:underline;color:#06c} 伪类 | 2 | 1 | 45 | 53 | 4 |
| 4 | @media(min-width:768px){.x{display:flex;gap:8px}} 嵌套 | 2 | 2 | 49 | 68 | 6 |
| 5 | content:"a;b" 含分号 | 2 | 1 | 34 | 42 | 4 |
| 6 | content:"{}" 含花括号 | 2 | 2 | 38 | 46 | 4 |
| 7 | /* a;b;c */ 注释含分号 | 2 | 1 | 34 | 43 | 5 |
| 8 | /* {;} */ 注释含花括号 | 2 | 2 | 31 | 40 | 5 |
| 9 | :root{--gap:8px;--fg:#333} 自定义属性 | 2 | 1 | 26 | 34 | 4 |
| 10 | calc() 与 url("a;b.png") | 2 | 1 | 53 | 61 | 4 |
| 11 | linear-gradient() 多参数 | 1 | 1 | 58 | 64 | 3 |
| 12 | url(data:...;base64,) data URI | 1 | 1 | 64 | 70 | 3 |
第 5、7、12 行是本页存在的理由:这三行里的分号如果被当成分隔符,输出会明显不对。你可以在输入框里粘第 12 行那个 data URI 片段试一下,输出里 data:image/svg+xml;base64, 会完整保留、一个字符不少——如果它被切开了,说明保护逻辑被破坏了。
边界与精度
- 不处理未闭合的花括号。写成
a{color:red(少一个})时,状态机扫到文件末尾仍有一帧没被收口,页面会提示「有 1 个左花括号没有匹配,请检查是否漏写了}」,并把未闭合的内容照原样输出但缩进可能偏深。真实 CSS 解析器会直接报错,这里选择不报错是为了让你还能看到文件尾部的内容。 - 多余的右花括号(
a{color:red}})不会导致崩溃,深度用Math.max(0, depth-1)兜底,但页面会提示多了 1 个}。 - 制表符与空格的混用会被统一。如果输入里有的地方用 Tab、有的地方用空格,选 2 空格档位会把它们全部规范成 2 空格。这是刻意的:混用是缩进错乱的根源,但要知道这确实改动了空白字符。
white-space类属性不受影响,因为工具不碰属性值。 - 不会动引号类型。双引号不会被换成单引号,
content:""重排后还是双引号。这是故意的——改引号虽然安全,但会让 diff 变长,违背「格式化只动空白」的原则。 - 选择器里的逗号不拆行。
h1,h2,h3重排后仍是h1,h2,h3写在同一行,不会变成三行。AST 工具常会拆成多行,本页不这么做,因为那会让「选择器」和「声明」两种内容混在同一段缩进里,反而更难读。 - 属性值里的
;永不切分,因为「声明」在状态机里的定义是「引号外、注释外、url() 外的分号」。这条规则同时保证了grid-template-areas:"a a" "b b";这种多行字符串值不被破坏。 - 体积增长的量级可预估。每条声明独立成行会多出
缩进宽度 + 1 个换行符字节,展开后体积增加约 20%~30%(示例是 24.0%)。所以格式化后的文件不要直接上线,要先过一遍压缩。
常见问题
格式化会不会改变我的 CSS 行为?
不会改变语义,只改空白和换行——包括补上省略掉的末尾分号(CSS 本来就允许省略,补上等价)。但有三件事它确实会动,你得知道:一是缩进空白,选 2 空格会把原本的 Tab 和 4 空格全换成 2 空格;二是在「压成一行」模式下,原有的换行会被合并成一行,如果你的 HTML 里有 <pre> 包着这段 CSS,显示会变;三是如果你在字符串或注释里手写了换行(多行 data URI、多行注释),压缩成一行会把它们接在一起——这种内容按 CSS 语义来说空白不敏感,但如果你在注释里写了版权信息、指望靠换行排版,那会失效。
为什么不做 AST 解析,那样不是更准吗?
更准,但代价不成比例。AST 方案要引入完整的 CSS 语法分析器,必须处理 @supports 条件、--自定义属性(它的值按 CSS Variables 规范可以是任意 token 序列)、@media 查询里的复杂表达式、以及各种厂商前缀残留;更麻烦的是现代 CSS 语法还在演进,解析器遇到不认识的语法就会抛错,反而更不可靠。文本级路线的核心优势是保真:它不「理解」任何东西,只把不在保护上下文里的分号和花括号当成结构标记,所以你写什么它就格式化什么,绝不会因为「看不懂」而丢弃内容。对「让压缩过的 CSS 能被人 review」这个目标来说,够用了。
我的 data URI 背景图被切碎了,怎么办?
先确认它是不是在未加引号的 url() 里。正确写法是 url(data:image/svg+xml;base64,PHN2Zy8+) 或 url("data:image/svg+xml;base64,PHN2Zy8+"),两者页面都能正确保护——引号串和未加引号 url() 是两个独立的保护上下文。如果你写成了 background:url(data:image/svg+xml;base64,...) no-repeat 这样的简写,中间的 no-repeat 是在 url() 括号外面的,状态机读到 ) 就结束保护,后面的分号仍按声明分隔符处理,这是正确的。真正出问题的写法是自己手写 CSS 时把 base64 串换行了(data URI 允许换行,但换行符要写成 %0A 而不是真的回车)。
注释会被删掉吗?
不会。块注释 /* ... */ 原样保留,占独立一行(前面按当前深度缩进),内部字符一个不改。这和「CSS压缩工具」的行为正好相反——压缩会删掉所有注释以省体积。本页的用途是给人看,所以注释是重要的上下文(比如 /* 覆盖上面的规则,勿删 */)。唯一不被识别的是 // 行注释,因为它不是合法 CSS,浏览器解析时会直接报错,页面遇到它会给出提示。
「声明内按属性名排序」为什么在展开模式下没反应?
因为展开模式下每条声明已经独占一行,没有「一组声明」这个对象可供排序。排序只在「压成一行」模式下有意义:这时一条规则里的多条声明挤在同一行,顺序才有可读性价值。算法是在规则收口时(弹帧那一刻)把这层的声明按冒号前的属性名字典序重排,选择器固定在首位。实测示例的第一条规则排序前后都是 425 字节,顺序从 padding; border-radius; background; box-shadow 变成 background; border-radius; box-shadow; padding。注意排序不改变字节数,只是换个顺序——如果你要的是「按字母序 review 便于对照规范」,这个功能有用;如果你期待它能减小体积,那是误解。
格式化后体积涨了 24%,上线会不会变慢?
会影响,但通常可以忽略,而且正确做法本来就不该直接上线。示例里 383 字节涨到 475 字节,对单次请求来说几 KB 的差异几乎测不出来——真正影响加载的是图片和字体。更重要的是:未压缩的 CSS 会让 gzip/br 的压缩率变差,因为杂乱空白让重复模式(相同的属性组合、相似的选择器)更难被熵编码器识别。经验做法是「格式化用于 review 和维护 → 提交前用构建工具压缩 → 线上跑压缩版」。你可以在本页点「压成一行」看一眼那一版的字节数,它介于两者之间,比展开版小但仍不是最优压缩。
支持 SCSS / Less / Tailwind 语法吗?
部分支持,但不保证。这些预处理器有 CSS 之外的神语法:SCSS 的 $var、嵌套、@mixin,Less 的 @var、.mixin(),Tailwind 的 @apply 和 @layer。其中嵌套和 @mixin 这类花括号块,本页的状态机会当成普通规则处理并正确缩进;但 $var: value 这种以 $ 开头的行不会被识别为声明(因为它没有冒号前的属性名形态),可能出现错位。建议先让构建工具把 SCSS 编译成纯 CSS 再来格式化,这样能得到可靠结果。要把 Tailwind 的 @apply 类名转成实际 CSS 属性,请用「CSS转Tailwind转换器」做反向操作。
我的数据会传到服务器吗?
不会。重排完全在你浏览器里完成,工具只用了 String.prototype.replace、indexOf、slice 这些原生字符串方法,不发任何网络请求,也没有引入任何第三方库(既没有 CDN 脚本,也没有外部 .js 文件)。你在开发者工具的 Network 面板刷新页面,除了 HTML 和本页那个内联 script 之外看不到别的东西。粘进来的 CSS 不写入浏览器存储,刷新即清空。导出的 CSV 是在本地用 Blob 生成的,文件直接进你的下载目录,不经过服务器。
相关工具
- CSS压缩工具——本页的反向操作:把格式化后的 CSS 再压回一行、删注释、压掉空格,用于上线前减小体积。两页配合就是「格式化 → review → 压缩」的完整流程。
- CSS校验——检查语法错误、浏览器兼容性和无障碍问题。本页只管排版不管对错,属性名拼错、括号不闭合这类问题要用它查。
- HTML格式化——同样思路的文本级重排,处理 HTML 的标签缩进和属性换行,与本页可以配合做整站代码格式化。
- SQL格式化——把压缩的 SQL 展开成关键字大写、子句独立成行,适合 review 复杂查询。
- XML格式化——展开压缩的 XML,处理标签层级缩进,结构分析与本页的规则缩进是同一套方法。
- CSS转Tailwind转换器——把已有的 CSS 反向翻译成 Tailwind 工具类,适合老项目迁移到原子化 CSS 框架。
- CSS单位速查表——查
rem、em、vh、ch等单位的换算关系与浏览器支持情况,重排完 CSS 后核对单位是常用一步。