CSS样式覆盖实测:!important只有8.7%在盖第三方

CSS样式覆盖实测:!important只有8.7%在盖第三方
张文保 23 分钟阅读 4,955 阅读
本文目录
  1. 样式表里,!important的总量与密度是什么水平?
  2. 那11个零条站为什么其实并不干净
  3. 这些!important覆盖的类名归属于谁?
  4. 8.7%里最典型的覆盖形态是什么样
  5. 覆盖第三方样式只占8.7%,为什么和直觉差这么远?
  6. CSS样式表里,!important最常修饰哪几个属性?
  7. 写在媒体查询里的覆盖声明该不该计入?
  8. 同一属性上摞两条!important的重复声明有多少?
  9. 107个站里只有8个用了层叠层@layer,为什么?
  10. !important的字节账很小,真正的代价在哪?
  11. 样本口径如何让两个密度数字方向相反?
  12. 自查样式表的覆盖密度该按什么顺序盘?
  13. 先确认主样式表,再数密度
  14. 按选择器把覆盖归成三类
  15. 第三方那一类:不要删,收拢成显式的一层
  16. 框架工具类那一类:查构建配置,不逐条查代码
  17. 自己写的那一类:从重复声明查起
  18. 最后才引入层叠层
  19. 常见问题解答
  20. !important到底能不能用?
  21. 我的站上有几百条!important,算多吗?
  22. 删掉!important能提升页面速度吗?
  23. 用Tailwind的站!important更多,是不是说明Tailwind不好?
  24. !important会影响SEO排名吗?
  25. @layer能不能一劳永逸地解决这个问题?
  26. 为什么统计出来只有8.7%是在盖第三方,跟我的感受差这么多?
  27. 这批数据能不能代表我的站?
  28. 权威参考资料
摘要:把107个海外品牌站的样式表逐份拆开统计,一共30980条!important,每站中位92条,最多的一个站2745条。按每千条声明折算,密度中位数是20.9条,四分之一的站超过51.6条。真正反直觉的是它们覆盖的对象:命中第三方或平台类名的只有2709条,占8.7%;剩下91.3%落在这个站自己的类名上。这堵墙九成是自己砌给自己的。

写!important的时候都知道不该写,可写完当天问题就没了。等到三个月后要改一个按钮颜色,翻出来发现那行声明下面还压着两层!important,才想起来是自己当初留的。

关于它有个流传很广的解释:!important是被平台和第三方插件逼出来的。你装了一个评价插件,它的样式跟你的主题打架,你改不了它的源文件,只好在外面加一条!important盖住。这个解释顺理成章,也对得上多数人的记忆。另一轮实测里我量过图片格式这个决定归谁,那件事上平台确实没给站主留入口。样式这一头是不是同样如此,得靠数据判。

于是把107个站的样式表逐份拆开,每一条!important连同它所在的选择器一起数了一遍。数完的结果是,那个流传很广的解释基本站不住。

样式表里,!important的总量与密度是什么水平?

先看体量。107份样式表合计30980条!important,分布极不均匀:

口径数值
每站中位数92条
每站平均289.5条
最多的一个站2745条(charleskeith.com)
一条都没有的站11个
每千条声明的密度中位数20.9条
密度的四分位区间9.5条到51.6条

平均数是中位数的三倍多,分布是典型的长尾:多数站几十上百条,少数几个站把总量拉起来。密度最高的那个站每千条声明有235.8条!important,差不多每四条声明就有一条走最高优先级。

用密度而不用绝对数,是因为样式表大小差着一个数量级:charleskeith那份707KB,有些站只有几十KB,直接比条数等于在比谁的样式表大。上一轮量样式表覆盖率时遇到的是同一个问题,主样式表中位1149条class定义,这个基数不拉齐,后面所有比较都不成立。所以下文凡是横向比较,一律用“每千条声明多少条!important”。

那11个零条站为什么其实并不干净

这个看着很励志的结论先掐掉。11个零条站看着像模范生,把它们的样式表大小拉出来就露馅了:这11个站的样式表中位数只有6KB、78条声明,而其余96个站的中位数是212KB、5388条声明。wusthof.com那一份只有7条声明。

它们并非没写!important,而是我抓到的那份根本不是主样式表:首页上第一个出现的同域样式表,可能只是一个几KB的关键样式片段。这跟在线CSS编辑器吐出来的那份差异清单属于同一类误会,拿到一份文件不等于拿到了完整的那一份。

所以凡是涉及密度的统计,一律只算声明数超过200条的样式表,样本是92份。这么筛不是图数字好看,是怕一堆碎片把中位数拽下去。

这些!important覆盖的类名归属于谁?

这是整轮的核心问题,做法也简单:把每一条!important所在的那个选择器抓出来,看它命中的类名归谁。归类用了九组第三方与平台的类名前缀:Shopify自己注入的、评价插件、弹窗邮件、同意管理、在线客服、轮播UI库、支付与先买后付、站内搜索推荐,以及其余各类SaaS。

结果是这样的:

盖的是谁的类名条数出现在几个站
评价与UGC插件106017
轮播与UI库53851
同意管理弹窗36227
Shopify平台注入23820
其他SaaS插件18720
弹窗与邮件订阅16116
在线客服649
支付与先买后付566
站内搜索与推荐436
九类合计2709条,占全部的8.7%

九类加起来8.7个百分点。剩下的28271条,落在这个站自己的类名上。这个比例跟首页上挂着八个外部域那件事正好形成对照:第三方在网络层的存在感很强,在样式层的存在感小得多。

拿最极端那个站看一眼更清楚。charleskeith.com那份707KB的样式表里有13910条声明、2745条!important,其中命中第三方类名的只有37条。它排前面的几个覆盖选择器是.language_switch-list、.notification-wrapper、.account_menu_modal .notification-wrapper.show,对应语言切换列表、通知条、账户菜单,全是自家业务组件。这个站的覆盖不来自插件,来自自己跟自己反复较劲。

这个数字第一眼看着不可信,因为它跟每个前端的亲身记忆都对不上。可这两件事并不矛盾:被第三方逼着写的那几条,是你记得最清楚的;数量最多的那一批,恰恰是你写完就忘了的。印象里的比例按“痛苦程度”排序,实际的比例按“条数”排序,这两把尺子量的从来不是一回事。

8.7%里最典型的覆盖形态是什么样

占比不高,这一小撮的形态却很有代表性。taylorstitch.com有一条选择器是这样的:.yotpo-widget-referral-widget.yotpo-widget-override-css .yotpo-email-container .yotpo-input。中间那个类名yotpo-widget-override-css,是插件方自己造的、名字里带override的类,专门留给你去盖。供应商已经在类名里承认了你会来改它。

ikea.com那条更长:#onetrust-consent-sdk #onetrust-banner-sdk.otFloatingRoundedCorner.otRelFont.ot-bottom-left #onetrust-button-g,三个ID加三个类名串在一起,末尾还挂着!important。MDN关于特异性的说明里讲得很清楚,ID的权重比类名高一整个数量级,三个ID堆在一起基本已经是天花板。写到这个份上还要补一条!important,说明写的人并不确定自己的特异性够不够,索性一次加满。

另一类更好认:.shopify-payment-button__button--unbranded。这是Shopify注入的加速结账按钮,站主动不了它的源文件,想换个颜色只能从外面盖。hellotushy.com在这一个选择器上盖了16次。

覆盖第三方样式只占8.7%,为什么和直觉差这么远?

回答这个之前,得先承认我这把尺子的一个局限:它只认得出“别人的类名”,认不出“别人写的、但顶着你的类名的代码”。

一个Shopify站买了主题,主题里那几万行CSS不是站主写的,但类名全是.card__、.price__、.product-form__这种,在我的分类器里全部算作“自己的”。同样,用Tailwind构建的站,那些工具类是构建工具生成的,也算“自己的”。所以8.7%这个数应该读成:盖那些一眼能看出是外来户的东西,只占8.7%;剩下的都盖在“名义上归自己、实际上未必自己写”的代码上。

顺着这条线索分组,就得到了这一轮最有解释力的那张表。我按样式表里的框架痕迹给107个站分类:出现Tailwind的自定义属性和转义类名算Tailwind,出现栅格与组件类算Bootstrap,出现主题模板特征类算Shopify主题,都没有的算看不出框架。

样式表是谁生成的站数每千条声明的!important中位数
Bootstrap1060.7条
Tailwind3043.0条
Shopify主题2417.2条
看不出框架479.4条

用了框架的站,密度是没用框架的站的两倍到六倍半。这个差距比按电商平台分组时大得多。按电商平台分组的结果是这样:

建站平台站数每千条声明的!important中位数样式表中位体积
Salesforce828.1条743KB
Next.js1222.2条138KB
Shopify6519.9条146KB
识别不出平台209.2条86KB

最大与最小之间只差三倍,而且Shopify这个装插件最凶的平台排在中间。预测一个站有多少条!important,问它用什么CSS框架,比问它用什么电商平台准得多。

为什么框架反而更多?因为这两套框架的工具类本身就带!important。Bootstrap的工具类API文档里,important是生成器的一个正式选项;Tailwind的工具类文档里有个感叹号前缀,加上去这条工具类就带!important出来。这批数据里能直接看到证据:被!important修饰的属性排行里,--tw-bg-opacity出现444次、--tw-text-opacity出现336次,这两个名字只可能来自Tailwind生成的代码。

所以开头那个流传很广的解释要改一改。把你逼到写!important的,往往是你自己选的那套工具,而且它按设计就这么生成。这跟平台锁死一个功能是两码事:Bootstrap和Tailwind从来没有拦着你不加感叹号,开关一直在你手上。

CSS样式表里,!important最常修饰哪几个属性?

把每条!important修饰的属性拆出来数,前几名是这样:

属性次数出现在几个站
display180882
color144655
width138656
font-size115244
background-color105235
height98251

display排第一,而且铺得最开,107个站里82个都有。这一条的含义值得单独说:绝大多数display加!important做的都是同一件事,写法是display:none!important。有人在页面上藏了一块东西,而且藏得非常坚决。

这跟SEO有一点关系,但不是常被吓唬的那一种。用CSS藏内容本身不算作弊,问题在于藏得太彻底会让人忘了它还在。样式表里八成的规则这一页根本用不上,加上HTML空壳那一轮量到的形态,能拼出一个常见场景:结构还在DOM里、样式把它彻底摁住、没人再想起它,直到某天有人改了一行选择器,它突然冒出来。

紧随其后的color、font-size、background-color属于另一类,清一色的视觉细节。这些多半不是跟第三方打架打出来的,是设计稿改了三版、每一版在上一版外面又加了一层。

再往下还有一批更能说明问题的:margin-top、margin-bottom、padding-left、padding-right这四个方向的间距属性各出现七八百次,但每一个都只集中在十几二十个站上。这种“总量大、铺面窄”的形态,说的是少数几个站把整套间距体系用!important重写了一遍,通常发生在一个团队接手别人做的主题、又不敢改主题源文件的时候。选主题时该看代码而不是看演示图,多半就是为了避开这个局面。

写在媒体查询里的覆盖声明该不该计入?

把30980条就这么端出去,会有一个明显的反驳等着:响应式覆盖是正当用法,那部分不该跟“乱写”混为一谈。

这个反驳有道理,所以我单独数了一遍。写在@media块里的!important有12808条,占全部的41.3%;不过按站看,每站的这个比例中位数只有23.1%,又是长尾,少数几个站的响应式覆盖特别重。

把这四成剔掉,剩下的一万八千条仍然是在无条件地覆盖。写在媒体查询里也不等于就正当:干净的响应式写法是让不同断点的规则各管各的,靠选择器结构分开;需要靠!important才压得住,通常说明断点之间的规则本来就在互相打架。媒体查询是这条声明出现的位置,不是它的免罪符。

同一属性上摞两条!important的重复声明有多少?

再看一个更能说明问题的指标:同一个选择器、同一个属性,被!important声明了两次以上。这时候两条声明都带着最高优先级,胜负只能由谁写在后面来决定,MDN对这个关键字的说明里把这个规则写得很直白。

这样的重复声明一共1714条,出现在64个站上,单站最多的govee.com有328条,也就是一个站上有328个属性被摞了两遍。这意味着有人为了盖住一条!important,又写了一条!important。到这一步,优先级机制已经完全失效,剩下的只有文件顺序在起作用。

顺带看一眼这些声明在文件里的位置。我算了每条!important在样式表字节流里的相对位置,每站的中位数落在0.444,不偏不倚在文件中间,落在最后10%字节里的只占7.9%。这跟“覆盖层都是后来追加在文件末尾”的印象不符。原因不难想:这些样式表大多是构建工具打包出来的,源文件的先后顺序在打包后已经被打乱,追加的那一层未必排在最后。

107个站里只有8个用了层叠层@layer,为什么?

这几年CSS其实给出了一个正经答案,叫层叠层。它跟那批停在四年前的前端库面对的是同一个处境:新办法早就有了,只是没人回头去换。MDN对@layer的说明里讲的思路是:把第三方样式、框架样式、你自己的样式各放进一个命名的层,层与层之间的优先级由你显式排定,不靠选择器谁写得更长来拼。这正好对着上面那些症状。

107个站里,用了@layer的只有8个。用了它也不等于就干净:把这8个站放回按密度排序的队列里,它们从第12名一直散到第71名,高的那个每千声明78.9条,低的只有8.8条。层是个组织工具,不是个清理工具。剩下99个还在用旧办法解决这个问题,旧办法的痕迹我也量了:写html body这种前缀硬提权的有3个站;ID选择器每站中位6条、最多的一个站168条;四个以上class串在一起的选择器,每站中位79条,最多的一个站2931条。

那个2931条的站,等于把三千条规则的优先级都赌在选择器长度上。这类超长选择器还有个副作用:它们把样式和DOM结构死死绑在一起,结构一动样式就失配,这也是用不上的规则越积越多的成因之一。这更像组织问题,一个团队在没有约定的情况下各写各的、互相加码,自然会走到这一步。MDN对层叠机制的解释里那句话说得挺好:层叠是一套用来解决冲突的规则,而不是一套用来制造冲突的规则。

!important的字节账很小,真正的代价在哪?

先把字节账算清楚,免得把它当成一笔大账。

!important这十个字符,占样式表体积的比例,中位数是0.408%,最大的一个站5.820%。107个站合计约303KB,摊到每个站不到3KB。这笔字节账小得几乎不值一提。

所以别指望删掉它们能提速。跟那批给老浏览器发的兼容代码一样,真正的代价不在带宽上,而在别的地方:

第一是改一次的成本。样式表里每多一层!important,下一个人要改这块颜色就得多找一层。这个成本不出现在任何监控面板上,只出现在工时里。

第二是改版的风险。页面上那些没人清的老写法之所以能活很久,很大一部分原因就是没人敢动,覆盖层越厚,敢动的人越少。改版掉流量这件事里最难排查的一类,就是新旧样式互相覆盖导致某块内容视觉上消失、结构却还在。display:none!important正是这类事故最常见的凶器。

第三是它会传染。第三方脚本会自己漂移,而你写下的覆盖层不会跟着漂:插件改了个类名,你那条!important就变成一条永远不生效的死规则,还留在文件里占位置。一个人在项目里加了第一条!important,下一个人为了盖住它只能也加一条。前面那1714条重复声明,就是这条传染链留下的化石。

样本口径如何让两个密度数字方向相反?

这一轮踩的坑不在数据处理上,在样本口径上,而且它一度给了我两个方向完全相反的答案。

这107份样式表来自两个来源:49份是三周前那轮普查里存下的,取的规则是“该站class定义最多的那一份”;58份是这次现抓的,取的规则是“首页上第一份同域样式表”。这两条规则挑出来的不是同一个文件。

直接把两堆放在一起比,得到的是:老那批每千声明30.7条,新这批16.2条,看上去“主样式表更密”。但这两堆是不同的站,比出来的其实是两批站的差别,不是两种取法的差别。

于是我找了30个两份都有的站,同一个站的两份样式表各算一遍。结果主样式表27.0条、首页第一份38.6条,方向反过来了。这30个站里有6个两份其实是同一个文件,另有22个的主样式表更大。

结论很朴素,也有点扫兴:先前那个“老样本更密”的差异,是两批站不一样造成的,不是取法造成的。这条自查规矩这几轮反复救场:两个数不一样的时候,先确认它们量的是不是同一批东西,再去解释为什么不一样。Vary那一轮和前端库版本那一轮都是靠这一步把错结论掐在发布之前。

还有两条口径必须写明。每个站只取了一份样式表,一个站上完全可能有五六份,这一份代表主干但不代表全部。以及那49份是三周前的存档,中间这三周里它们可能发过版;对!important密度这种量级的指标,三周的漂移影响有限,但严格说它不是同一天的快照。

自查样式表的覆盖密度该按什么顺序盘?

这套盘点比图片格式那一套还简单,一个下午能做完。

先确认主样式表,再数密度

先确认自己手上那份是不是主样式表。前面11个零条站的教训摆在那儿:首页上第一个出现的样式表,可能只是个几KB的关键片段。看一眼声明条数,几十条的多半是碎片,几千条的才是主干。确认之后再数三个数:!important总条数、声明总条数、两者之比。有了每千声明的密度,才知道自己站在20.9这个中位数的哪一侧。低于10条基本不用管,高于50条说明这套样式已经开始靠蛮力维持。

按选择器把覆盖归成三类

把每条!important所在的选择器抓出来,按类名前缀分成三堆:一堆是第三方与平台的,一堆是框架工具类,一堆是自己写的业务类。这三堆的处置方式完全不同,混在一起看只会得出“太多了得删”这种没法执行的结论。

第三方那一类:不要删,收拢成显式的一层

这一堆确实没有源文件修改权,删了就打回原形。可行的做法是收拢:把它们集中到一个命名的层里,或者至少集中到一份文件里,让下一个人一眼看到“这些是用来盖外部插件的”。覆盖别人的东西本身不丢人,丢人的是把它们散落在两千行里。

框架工具类那一类:查构建配置,不逐条查代码

这一堆最容易被误判成“团队水平差”。如果你的密度高得离谱、而且属性里出现--tw-开头的自定义属性,那多半不是谁乱写,是构建配置里开了全局的important,或者团队养成了随手加感叹号的习惯。这一堆要改的是约定和配置,逐条删代码没有意义。

自己写的那一类:从重复声明查起

先找同一个选择器同一个属性上的重复声明,就是前面那1714条那一类。它们百分之百是历史遗留,因为一个属性在同一处不可能真的需要两次最高优先级。删掉靠后那条之前先确认视觉没变,这活儿一次删几条,别整批来。

最后才引入层叠层

层叠层是好东西,但它不是一个能顺手加上去的补丁。跟content-visibility那类可以直接加的属性不一样,它改的是整套优先级秩序。已有的几千条!important不会因为你套了一层@layer就自动降级:带!important的声明在层叠里的排序规则是反过来的,层的优先级越低反而越难被盖。所以顺序是:先把存量清到能看懂,再引入层,而不是指望层去收拾存量。

常见问题解答

!important到底能不能用?

能,而且有些场景就该用。覆盖你没有修改权的第三方样式、写工具类、做无障碍相关的强制覆盖,这三类都属于正当用途。判据不在于用没用,而在于用完之后下一个人能不能一眼看懂它为什么在那儿。

我的站上有几百条!important,算多吗?

看密度不看条数。每千条声明的中位数是20.9条,低于10条属于干净,20到50条属于常态,超过50条说明这套样式已经在靠优先级硬拼。别拿绝对数跟别人比,样式表大小差着一个数量级。

删掉!important能提升页面速度吗?

基本不能。这十个字符占样式表体积的中位数只有0.408%,一份300KB的样式表里也就1KB多一点。删它的理由是可维护性,跟性能无关。

用Tailwind的站!important更多,是不是说明Tailwind不好?

不是。Tailwind的工具类带!important是设计使然,加不加由你在类名前面写不写那个感叹号决定。密度高只说明这个项目里用得多,可能是团队约定问题,也可能是构建配置开了全局选项。这是配置层的事,跟框架好坏无关。

!important会影响SEO排名吗?

不会直接影响。它是一个样式优先级关键字,搜索引擎不会因为它给你加分或减分。跟样式表阻塞渲染那条链路也搭不上边,因为它不改变文件何时下载完。间接的关联只有一条:用display:none!important藏起来的内容,在渲染后的页面上是不可见的,而搜索引擎评估的是渲染结果,所以要留心自己藏掉的是不是重要内容。

@layer能不能一劳永逸地解决这个问题?

能解决新写的部分,解决不了存量。带!important的声明在层叠层里的优先级顺序是反的,套一层@layer不会让已有的几千条自动失效。实际路径是先清存量再引入层。

为什么统计出来只有8.7%是在盖第三方,跟我的感受差这么多?

因为记忆按痛苦程度排序,统计按条数排序。被第三方逼着写的那几条你会记一辈子,自己顺手加的几百条写完就忘。另外这把尺子只认得出“别人的类名”,主题和框架生成的代码顶着看起来像自己的类名,会被算进那91.3%里。

这批数据能不能代表我的站?

样本是107个海外品牌电商站,Shopify占大头,密度分布带着平台色彩。能迁移的是方法:数密度、按选择器归类、找重复声明这三步,在任何一份样式表上都能跑,半天就能得出自己那份答案。

权威参考资料

分享到
标签
版权声明

本文标题:《CSS样式覆盖实测:!important只有8.7%在盖第三方》

本文链接:https://zhangwenbao.com/css-important-override-attribution-audit.html

版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交