未使用 CSS 怎么查才不误删:静态匹配、动态类与页面覆盖
清理 unused CSS 不能只看一张静态页面。本文说明选择器匹配、hover 等动态状态、运行时类、路由覆盖与逐批删除流程。
打开配套工具 →旧项目的 CSS 往往只增不减。换过几轮页面、主题和组件后,样式文件里会留下看似没人使用的 .old-banner、.legacy-modal 和很长的后代选择器。把它们全部删掉,文件确实会变小;问题是某个只在登录失败、移动端菜单或节日活动出现的状态,也可能一起消失。
未使用 CSS 选择器检查工具适合先做页面级审计:输入 HTML 与相关 CSS,它会区分已命中、未命中、动态状态和无效选择器,并保留 @media、@supports、@layer 等上下文。它输出的是候选清单,不会直接生成一份冒险的 clean.css。
“未使用”必须先说明范围
对一份静态 HTML 来说,选择器没有匹配元素,可以准确描述为“当前文档未命中”。但这和“整个网站永远不用”不是一回事。
一条规则可能出现在:
- 其他路由或分页;
- 登录、错误、空状态和权限状态;
- 点击后才插入的弹窗、菜单和 Toast;
- CMS 编辑器稍后生成的内容;
- A/B 测试、地区或用户分组;
- iframe、Web Component 的 Shadow DOM;
- 第三方脚本添加的类名;
- 打印样式、深色模式和特定视口。
因此一次静态检查只能回答“这份样本是否命中”。工具越诚实地说明范围,结果越有用。
hover、focus 和伪元素为什么容易被误判
下面这条规则在页面刚加载时未必成立:
.button:hover::before {
opacity: 1;
}:hover 要等指针进入元素,::before 也不是 DOM 里的普通节点。如果分析器直接执行 querySelectorAll('.button:hover::before'),很可能得到空结果,甚至因为伪元素而报错。
更合理的静态处理是先看基底 .button 是否存在,再把整条规则标为动态确认。:focus、:checked、:target、:valid、:open、:fullscreen 等状态也应采用相同思路。
基底存在,不代表状态样式一定正确;基底不存在,也不代表其他页面没有。它只是比简单的“0 个元素,所以删除”多了一层必要的判断。
JavaScript 动态类需要保留词
很多组件不会把完整状态写在初始 HTML 里:
document.documentElement.classList.add("js-ready");
dialog.classList.toggle("is-open", open);静态样本里看不到 js-ready 和 is-open,对应样式却可能是核心交互。审计前可以从代码搜索、组件文档和运行时 DOM 中整理保留词,例如:
js-ready
is-open
modal-open
loading保留词不应靠猜。把 active、open、hidden 等所有常见词一股脑加入,会让报告失去筛选价值。更好的办法是搜索 classList、模板条件、组件状态映射和第三方插件文档,记录真实出现的动态类。
选择器列表要逐项判断
一条 CSS 规则可以包含多个选择器:
.article-title,
.legacy-title,
.modal-title:hover {
font-weight: 700;
}其中第一项可能已使用,第二项未命中,第三项依赖动态状态。把整条规则简单标成“使用”或“未使用”都不够准确。审计结果应把逗号列表拆开,分别记录命中数和判断原因。
真正改代码时,也不能只删掉整个声明块。应该保留使用中的选择器,移除确认无用的成员,并继续保持声明顺序和所在条件不变。
@media、@supports 和 @layer 不能被抹掉
下面的 .sidebar 在窄屏不会生效,但仍可能用于桌面:
@media (min-width: 1024px) {
.sidebar { display: block; }
}静态匹配可以确认 HTML 有没有 .sidebar,却不能只根据当前窗口判断媒体条件是否有价值。@supports 与 @container 也一样。@layer 还会影响级联顺序,移动规则或重新输出 CSS 可能改变最终样式。
因此报告应保留条件上下文。清理时在原文件里修改,而不是把所有“已使用规则”拼成一份丢失结构的新 CSS。
浏览器 Coverage 和静态匹配各自解决什么
Chrome DevTools 的 Coverage 会记录当前实际执行过程中哪些 CSS 字节被使用。它适合走一遍真实交互、不同视口和路由,发现运行时覆盖情况。但没触发到的状态仍会显示未使用,单次操作也无法覆盖所有用户路径。
静态 HTML 匹配可以快速解释每条选择器为何命中或未命中,适合代码审查和小样本。它看不到 JavaScript 后续生成的状态。
两者结合更可靠:静态报告先缩小范围,Coverage 再覆盖真实交互,最后用项目全局搜索确认类名来源。
一套不容易翻车的清理流程
先建立基线:记录生产 CSS 大小、关键页面截图和自动化测试结果。选择一个页面或组件,而不是一次处理整个站点。
把对应 HTML 与 CSS 放进审计工具,给真实动态类配置保留词。先处理语法无效和明显属于已下线模块的选择器,再检查未命中候选。
对每个候选执行三步:
- 在整个仓库搜索类名、ID 和模板片段;
- 在不同路由、视口和交互状态查看运行时 DOM;
- 小批量删除后跑视觉回归、端到端测试和生产构建。
每一批都单独提交。出现问题时可以准确回滚,不需要从一次删除几千行的提交里找原因。
总结
未使用 CSS 不是一个只靠正则或单页扫描就能得出的绝对结论。真正可靠的清理需要明确样本范围,区分动态状态,保留条件上下文,并把静态检查、运行时 Coverage、仓库搜索和回归测试连起来。
把工具当作审计助手,而不是自动删除器。这样 CSS 会逐步变小,页面也不会在某个没人记得的状态里突然失去样式。