杂记 › 技术笔记 › CSS 压缩没吃掉 calc 的空格,美化却把 .btn:hover 劈成了两截

CSS 压缩没吃掉 calc 的空格,美化却把 .btn:hover 劈成了两截

一、排完版上线,悬停态集体失效

压了好几年没人动的样式表,接手第一件事通常是排一遍版。243 字符的输入美化出来是 291 字符、26 行,每条属性独占一行,看着比原来舒服。上线当天反馈就到:按钮没有悬停态,列表项的前缀图标也不见了。

逐行比对输出,根因落在 .btn: hover。源文件写的是 .btn:hover,冒号后凭空多出一个空格,选择器当场失配;同一段的 li::before 出来是 li: : before。单把 .btn:hover{opacity:0.8}li::before{content:""}a:focus{outline:none} 丢进 CSS 美化压缩,66 字符变 90 字符、12 行,三个伪类无一幸免——那页首屏预填的样例本就带着这条伪类,点一次美化就复现。

二、美化只认字符,不认冒号的身份

CSS 里冒号至少有四种身份:伪类与伪元素、属性与值之间、媒体特征里、字符串内容里。美化对四种一视同仁,看见冒号就补一个空格:补在属性上是可读性,补在媒体条件里碰巧仍合法,补在伪类上是语法事故,补在引号里直接改了内容。69 字符的 @media (max-width:600px){.a{color:red}.b{padding:calc(100% - 4px) 0}} 美化成 87 字符、11 行,条件冒号被补空格、左花括号后紧跟空行。

JS 美化压缩那篇的破坏点在分号与换行,标识符与冒号之间没有语义绑定;CSS 的伪类偏偏靠这个紧贴的冒号活着。

美化前 66 字符 美化后 90 字符 12 行 .btn:hover 变 .btn: hover li::before 变 li: : before 补出的空格让整条规则失效

图 1:伪类样例美化前后的字符与行数

三、缩进那一栏,三档输出逐字节相同

没有缩进,第一反应是去动「缩进方式」,那里确实摆着 2 空格、4 空格、Tab 三档。可同一段样例在三档下都是 291 字符、26 行、每行顶格在 0 列,逐字节没有差别:字符循环里算过缩进,随后一次逐行 trim 全抹掉,收尾再用恒为 0 的层级终值补回去。切到压缩档时这一栏只是变淡(实测 opacity 0.5),下拉框没被禁用,改完照样什么都不发生。

四、真正保守的是压缩:calc 的空格它没动

两桩先入为主都不成立。一是 calc():.box{width:calc(100% - 20px);padding:calc( 10px - 5px )} 60 字符压到 56 字符,减号两侧空格一个没丢,多写的被归一;反方向也不替你补,无效的 calc(100%-20px) 27 字符压完一字未改。

二是 IE hack:.ie6{*zoom:1;_height:1%;width:10px} 35 字符压完原样,前导 * 与 _ 没被当非法声明吞掉。!important 同理,red !important 与 none!important 原样混排出来,47 字符压完仍是 47 字符。

「去除所有空白」同样不成立:压缩只删花括号、冒号、逗号邻边的空白与换行,唯一改动可见内容的是字符串,21 字符的 .s{content:"x, y; z"} 压成 19 字符。

五、/*! */ 没有特权,去留只在那一个勾选上

打包工具普遍把 /*! */ 当保留注释,这版没有特例:它与普通注释走同一个匹配分支,去留只由那个默认勾选决定。/*! keep me: v1.0; license */ 配一条普通注释和 body{color:red} 共 64 字符,取消勾选压到 15 字符、少 76.6%,许可头跟着消失;勾着压是 61 字符、只降 4.7%,注释内部照样被改写,keep me: v1.0 成了 keep me:v1.0。这跟 SQL 美化 那篇的「注释去哪了」是同一类口径:勾选框只决定有无。

六、字符串和 url() 里的分号,被当成声明分隔符

美化的第二个破坏点在最不该断的地方。56 字符的 .i{background:url("data:image/png;base64,iVBORw0KGgo=")} 美化成 65 字符:base64 那串在分号处拦腰断开换行,data: 后面也被补出空格。那 49 字符的协议地址也不放过,出来是 url(https: //www.uutools.com/a.png)。内联图标经过美化不是排版变差,是整条资源丢掉。

七、美化不幂等,压缩幂等

粘回输入再跑一遍,字符数还会涨:默认样例第一遍 291、第二遍 310,多出的 19 正是输出里的冒号数,第三遍仍是 310;calc 例子 64 到 67 再 67,@media 例子 87 到 93 再 93。压缩四组样例首跑即稳定,243 字符连压三遍都是 243。

缩进 2 空格 291 字符 缩进 4 空格 291 字符 缩进 Tab 291 字符 美化第 2 次 310 字符 美化第 3 次 310 字符 三档缩进输出一条线,全部顶格 压缩三遍都停在 243 字符

图 2:缩进三档与美化两次迭代的输出

八、体积收益要另算,统计条也不是字节表

2000 条规则、91780 字符的样式表压完是 89780 字符,只降 2.2%、耗时 0.8 ms;同一份美化到 107779 字符、10000 行,涨 17.4%、耗时 211.6 ms。输入本就没什么多余空白时,压缩档就只有这个量级。

统计条还要换算口径:数的是 UTF-16 码元不是字节,含中文的样例报 25 字符、UTF-8 实际 33 字节。美化档的「变化」是净增,那 243 字符显示 +19.8% 并标红,零变化写成 +0%,异常时结果 0 字符渲染成绿色的 -100.0%。

现象先查哪一处实测数字
悬停态失效选择器冒号后多出的空格66 字符变 90 字符
缩进改了没反应三档是否真的改变输出291 字符、26 行、顶格
内联图标消失data URI 的分号是否被断行56 字符撑成 65 字符
calc 减法报错减号空格原本写没写60 压到 56、27 一字未改

Q:怎么第一时间找出被劈开的伪类?

盯冒号后面那个多出来的空格:.btn:hover 变 .btn: hover、li::before 变 li: : before。这一版不校验合法性,非法输入照样走完美化且不提示,行数从 1 涨到 26 不等于结果可用。

Q:压缩会把 calc 的空格吃掉吗?

不会:60 字符压到 56 字符,calc(100% - 20px) 原样;它也不替你补,无效的 calc(100%-20px) 压完仍是 27 字符。IE hack 与 !important 的空格同样不动,会出事的是美化档。

Q:想要 4 空格缩进,这一页给得了吗?

给不了:三档对同一段样例都输出 291 字符、26 行且全部顶格。这一档只用来看结构,上线前按伪类与 data URI 这两处自查。

📎 相关文章

文本合并后行数对不上:缺尾换行、被吃掉的 BOM 和不可靠的顺序 2026-10-26 Excel 粘贴转 JSON:日期、长数字和精度在「粘贴那一刻」就已经变了 2026-10-26 条形码解码只吐原文:EAN-13 校验位、UPC-A 的前导零与 CODE128 的模 103 2026-10-26 二维码解码实测:纠错等级救不了遮挡,每模块 2 像素才是生死线 2026-10-26 字频还是词频:同一句中文,一份 23 项榜单和一份空表 2026-10-26