前端老鸟的血泪史CSS优先级权重总打架这套土味心法让你彻底躺平前端老鸟的血泪史CSS优先级权重总打架这套土味心法让你彻底躺平别整那些虚头巴脑的开场白咱们先聊聊为什么你写的样式永远被覆盖扒一扒CSS权重那点潜规则那些让你抓狂的权重计算陷阱实战里的宫斗大戏怎么破遇到样式不生效时的急诊室操作几个让同事直呼内行的野路子最后唠叨两句心里话前端老鸟的血泪史CSS优先级权重总打架这套土味心法让你彻底躺平别整那些虚头巴脑的开场白说实话写CSS这事儿吧表面上看着简单不就是调调颜色、改改间距嘛。但真干过的人都知道这玩意儿水深得一批尤其是当你打开控制台看到自己写的样式被一道横线划掉的时候那种血压飙升的感觉懂的都懂。我记得有次上线前夜产品经理突然说要改个按钮颜色。我心想这还不简单两分钟的事儿。结果写了color: red刷新一看纹丝不动。加!important没用。改成行内样式还是没用。最后发现是某个第三方UI库在更深层的嵌套里用了更变态的选择器还绑了个动态生成的类名。那一刻我真的想辞职去送外卖。这种场景你是不是也经历过明明代码写得规规矩矩页面就是不给面子。CSS优先级这玩意儿就像你家里那堆遥控器理论上每个都能控制电视但真用起来你得知道哪个才是正宫娘娘哪个只是答应常在。搞不清楚这个关系你就永远在跟浏览器玩猜拳游戏。今天咱们不聊什么高深的架构设计就掰开了揉碎了讲讲CSS权重这套江湖规矩。不是什么官方文档的复读机全是这些年踩坑踩出来的血泪经验。看完不敢说让你成为CSS大神但至少下次再遇到样式被覆盖你能知道该去骂谁。咱们先聊聊为什么你写的样式永远被覆盖这事儿得从浏览器的工作原理说起但别紧张我不准备给你上课。简单来说当浏览器拿到一堆CSS规则后它得决定听谁的。想象一下你同时收到了老板、老板的老板、以及老板他爹的指令都让你干不同的事你怎么办你肯定得排个优先级吧谁官大听谁的。浏览器也是这么想的。但它比较耿直不搞人情世故只看数字大小。每个选择器在浏览器眼里都是一串四个数字的组合(a, b, c, d)。这串数字越大话语权越重。不过咱先别急着算数先说说最让你抓狂的那个场景为什么有时候加了!important还是没用很多人以为!important是尚方宝剑见官大三级。这话对也不对。!important确实能掀翻普通规则的桌子但如果对方也用了!important呢这时候就回到了原点还是得比权重。更恶心的是如果同样的选择器都加了!important后面加载的会覆盖前面的——这玩意儿还有后浪推前浪的属性。还有种情况是你的!important写在了浏览器默认样式或者用户代理样式表里被用户的自定义设置给覆盖了。虽然这种情况少见但真遇到了能让你怀疑人生。再说说那个经典的误区行内样式就是写在HTML标签style属性里的那种是不是最牛逼的在普通规则里确实是的它的权重是(1, 0, 0, 0)比ID选择器的(0, 1, 0, 0)还高。但注意这只是普通规则范畴。一旦遇到!important行内样式也得跪。而且行内样式还有个致命缺点——不好维护不好复用不好调试。用多了就是给自己挖坑。我之前维护过一个老项目前任开发者特别喜欢写行内样式页面跟打补丁似的到处都是style...。后来需求改了几轮同一个元素上堆了四五行行内样式还互相覆盖控制台里看Computed面板那个计算过程跟解谜游戏一样。所以啊行内样式这玩意儿救急可以当饭吃不行。扒一扒CSS权重那点潜规则好了该上硬菜了。咱们来聊聊那个传说中的四元组(a, b, c, d)到底是啥。别被这一串零吓到其实就是个简单的比大小游戏。从左到右高位优先只要左边数字大右边不管多大都没用。就像比电话号码区号大的肯定比区号小的厉害不管后面几位是什么。a行内样式这个位要么是0要么是1。有行内样式就是1没有就是0。简单粗暴。前面说了行内样式是家里有矿的富二代在这个维度上直接碾压其他所有选择器除了带!important的。bID选择器就是#header这种。一个ID算1两个ID算2以此类推。ID选择器算是有点背景的官二代虽然比不过行内样式这个暴发户但在普通选择器里已经算是横着走的存在了。c类选择器、属性选择器、伪类这仨是平级的都在c这个位置算数。.nav、[typetext]、:hover、:nth-child(2)这些都算。每出现一个就加1。这一档算是中产小资数量多了也能压死人。d标签选择器和伪元素div、p、::before、::after这些。也是每出现一个加1。这就是纯纯的打工人单个战斗力很弱但胜在数量可以堆。通配符*和关系选择器*、、、~这些还有:not()本身注意:not()里面的内容是要算的都是0。对就是连户口都没有的黑户白给。来几个实战例子咱们一起算算/* 例子1简单的类选择器 */.button{color:blue;}/* 权重(0, 0, 1, 0) *//* 例子2ID加类 */#nav .active{color:red;}/* 权重(0, 1, 1, 0) *//* 例子3嵌套的标签和类 */div.container p span{color:green;}/* 权重(0, 0, 1, 3) - 1个类3个标签 *//* 例子4复杂的组合 */#app .sidebar .menu-item:hover a::before{content:→;}/* 权重(0, 1, 3, 2) - 1个ID (#app) - 3个类/伪类 (.sidebar, .menu-item, :hover) - 2个标签/伪元素 (a, ::before) */看到没例子4虽然写得老长但权重也就(0,1,3,2)。这时候如果有个(0,2,0,0)的选择器两个ID哪怕写得短也能把例子4干趴下。这就是为什么有时候你写了一堆类还不如人家一个ID硬气。这里有个坑我得特别提醒伪类里的:is()、:where()、:has()这些新玩意儿它们的计算方式有点特殊。:is()和:has()会取里面选择器的最高权重而:where()永远是0。比如/* :is()取最高值 */:is(#id, .class){/* 权重按#id算(0,1,0,0) */}/* :where()永远是0 */:where(#id, .class){/* 权重(0,0,0,0) */}:where()这玩意儿挺有意思相当于隐身模式适合写一些基础样式不想干扰其他权重的计算。但兼容性嘛…你懂的IE肯定是不支持的连一些老版本的Edge都悬。那些让你抓狂的权重计算陷阱理论归理论真写代码的时候坑比你想的多得多。陷阱一类名堆叠的幻觉很多人觉得.class1.class2.class3这种写法三个类加起来权重是3肯定比一个ID权重1厉害吧错ID选择器在b位类选择器在c位b位的1永远大于c位的任何数字。所以(0,1,0,0)(0,0,3,0)。这就好比正科级再牛逼也只是科级副处级再垃圾也是处级官大一级压死人。但等等这里有个例外——如果真的是同一个元素上同时有这三个类呢比如div classclass1 class2 class3这时候.class1.class2.class3确实能选中它而且权重是(0,0,3,0)。但即便如此它还是干不过#id。陷阱二!important的滥用我知道当你搞不定样式的时候加个!important就像吃了颗速效救心丸立马见效。但你要知道这玩意儿是会上瘾的。今天加一个明天加两个后天整个项目的CSS就变成了!important大乱斗。更恶心的是一旦用了!important再想覆盖它就得用更具体的选择器再加!important或者直接用JavaScript操作style属性。这就是典型的军备竞赛最后代码变成屎山谁动谁死。我见过最离谱的代码一个项目里搜!important能搜出几百个甚至有的选择器链写了五六个ID最后还要加个!important。问他为什么他说不加就不生效。大哥你都写五六个ID了还加什么!important啊这明显是架构出了问题啊。陷阱三继承的权重迷思继承来的样式权重是多少答案是0或者说比任何具体选择器都低。比如body{color:#333;font-size:14px;}.item{color:red;}HTML结构bodydivclassitem这段文字是什么颜色/div/body.item的权重是(0,0,1,0)而body的color是继承给.item的。继承的样式在权重计算里就是个弟弟哪怕你爷爷body传下来的规矩也抵不过亲爹.item的一句话。所以这段文字是红色不是#333。但注意如果.item没有设置color那它就会继承body的#333。这时候如果有个* { color: green }文字会变成绿色因为通配符虽然也是弟弟权重(0,0,0,0)但它直接作用于.item比继承的优先级高。这里再插一嘴font-size这个属性。它跟color还不太一样涉及到相对单位的计算。如果父元素是14px子元素设置1.2em那它计算出来是16.8px。但如果这时候有个权重更高的选择器给子元素设置了font-size: 16px那1.2em就被覆盖了不是继承父元素的14px再计算而是直接用16px。这个细节很多人搞混导致字体大小忽大忽小。陷阱四动态类名的坑现在用React、Vue这些框架类名经常是动态生成的比如CSS Modules或者Styled Components。你看到的类名可能是.Button_module_active__3x7a2这种看着就头疼。这时候调试权重你得先搞清楚最终渲染出来的类名是什么。有时候框架会给你加一堆工具类比如Tailwind那种权重计算变得特别复杂。更坑的是有些框架的样式是运行时注入的顺序不一定固定同样的代码两次构建样式覆盖关系可能不一样。我之前用某个UI库它有个Button组件我给它加了className覆盖样式本地开发没问题一上线样式丢了。查了半天发现生产环境下组件的样式注入顺序变了我的选择器权重刚好比它低了一点点。最后不得不把选择器写得更变态才解决。陷阱五layer的降维打击CSS新出了个layer规则这玩意儿简直就是权重的规则破坏者比!important还狠。它允许你定义样式的层级后面的层可以覆盖前面的层不管权重是多少。layerbase,components,utilities;layerbase{#app .button{color:blue;/* 权重(0,1,1,0)但在base层 */}}layerutilities{.red{color:red;/* 权重(0,0,1,0)但在utilities层 */}}虽然.red的权重比#app .button低但因为它在utilities层而utilities在base之后定义所以最终文字是红色。这相当于给CSS加了个时间维度权重计算变成了三维甚至四维的更绕了。不过layer目前支持度还行除了IE现代浏览器基本都支持了。用好了能很好地解决第三方库和自己样式的冲突问题但用不好…那就是给自己挖坑。实战里的宫斗大戏怎么破好了理论聊得差不多了咱们说说实战中怎么活命。场景一第三方库的样式把你覆盖了这是最常见的噩梦。你引入了一个UI组件库想改个按钮颜色发现怎么写都不生效。打开控制台一看人家用的是#antd-button .ant-btn-primary这种选择器权重(0,1,2,0)。你写.my-button { color: red }权重(0,0,1,0)根本打不过。下策是去改node_modules里的源码或者fork一份自己维护。这玩意儿维护成本极高升级库的时候能烦死你。中策是用同样的权重或者更高的权重去覆盖。比如你也用ID选择器#root .my-button或者堆类名.my-button.my-button.my-button虽然丑但管用。但这样搞容易陷入军备竞赛越写越长。上策是利用层叠上下文或者CSS变量。很多现代UI库都提供了CSS变量Custom Properties来定制主题。比如/* 覆盖Ant Design的主题色 */:root{--ant-primary-color:#ff4d4f;}这种方式不涉及权重计算直接修改变量值干净又卫生。如果库不支持CSS变量那就看看它有没有提供配置项在JS层面改样式而不是硬刚CSS。场景二故意把选择器写长有时候为了权重不得不写一些废话文学式的选择器。比如/* 本来可以这么写 */.active{color:red;}/* 但为了权重写成这样 */html body div.container ul.nav li.item.active{color:red;}后面这个选择器权重(0,0,2,6)比前面的(0,0,1,0)高多了。但代价是什么性能损耗虽然现代浏览器优化得挺好基本可以忽略以及可读性极差。后来者看到这段代码会想写这么多标签干嘛删掉几个试试一删样式就崩。所以这种写法要加注释告诉后来人“别动动就是为了权重”。场景三BEM命名法的妙用BEMBlock Element Modifier命名规范很多人以为是用来装逼的显得代码很专业。其实它在权重控制方面有大用。BEM的核心是只用类选择器不用ID不写嵌套。比如/* BEM写法 */.card{}.card__title{}.card__title--highlighted{}这种写法所有选择器都是类权重都是(0,0,1,0)看起来很平等避免了权重战争。但问题来了如果我想覆盖.card__title的样式怎么办用同样的选择器后加载的覆盖先加载的简单粗暴。而且BEM的命名本身就带有层级关系card__title一看就是card的title比.card .title这种嵌套选择器语义更清晰权重还低后者是(0,0,2,0)。当然BEM也有缺点类名太长HTML看着臃肿。而且严格遵循BEM的话有时候要写很多重复的类名。但权衡之下对于大型项目BEM还是利大于弊的。场景四CSS Modules和Scoped的隔离如果你用Vue或者React肯定用过scoped或者CSS Modules。它们本质上是通过给类名加哈希比如.button变成.button_a3f7来实现样式隔离。这种方式下你基本不用担心权重问题因为每个组件的样式都是独立的不会互相覆盖。但注意如果你用了深度选择器Vue的或者/deep/React的:global那就会打破隔离回到传统的权重战场。style scoped /* 这会作用于子组件 */ .parent .child { color: red; } /style而且scoped样式和全局样式混用的时候权重计算依然有效。比如你在全局写.button { color: blue }在组件里写.button { color: red }最终颜色取决于加载顺序和具体权重不是简单的组件样式优先。场景五不加!important赢过框架前面说了硬刚框架的权重容易陷入军备竞赛。那有没有优雅的办法有利用特异性Specificity的等价交换原则。框架用ID你就不用ID而是用更多的类选择器组合配合属性选择器。比如框架用了#app .btn权重(0,1,1,0)。你可以用[idapp] .btn.btn-custom权重(0,0,3,0)。虽然还是打不过但如果你能把选择器加载顺序控制在框架之后同样的权重下后加载的生效。更好的办法是用layer如果浏览器支持或者利用CSS的cascade特性——在同样的元素上后定义的规则覆盖先定义的。还有一种野路子利用伪类提升权重。比如.btn:hover比.btn权重高(0,0,2,0)vs(0,0,1,0)。虽然hover状态不一直触发但你可以用.btn:not(.btn)这种永远为真的选择器来 hack权重也是(0,0,2,0)。不过这种写法太猥琐建议只在万不得已时用且必须加注释。遇到样式不生效时的急诊室操作当你发现样式不生效的时候别急着删代码重写按这个流程来第一步打开控制台看Elements面板找到那个不听话的元素看右侧的Styles面板。被划掉的样式就是没生效的看看它是被谁干掉的。Chrome会显示具体的选择器以及Overrides被覆盖的提示。第二步看Computed面板这是你的验尸报告。这里显示的是最终计算出来的样式值以及它是从哪个规则来的。点击那个规则可以跳转到具体的CSS文件和行数。如果Computed里显示的值跟你写的不一样说明有更高权重的规则在作怪。这时候要一步步回溯看是哪个程咬金半路杀出。第三步检查选择器是否写对了有时候是拼写错误比如.botton写成.button或者类名大小写不对CSS类名是区分大小写的。还有可能是空格问题.nav .item后代选择器和.nav.item同时包含两个类完全是两码事。第四步看加载顺序同样的权重后加载的覆盖先加载的。检查一下你的CSS文件加载顺序或者style标签在HTML里的顺序。有时候是打包工具Webpack、Vite把CSS顺序搞乱了特别是用了动态导入import()的时候。第五步检查动态生成的类名如果是React、Vue这些框架看看实际渲染出来的HTML结构类名是不是你预期的那样。有时候条件渲染v-if、{condition Component /}会导致元素时有时无样式也跟着时灵时不灵。第六步查JS是否抽风有些样式是JS动态计算的比如根据窗口大小计算宽度或者根据API返回的数据加类名。如果JS报错了或者异步数据还没回来样式可能就挂掉了。这时候看Console面板有没有报错。第七步缓存问题浏览器缓存、CDN缓存、服务端缓存都可能让你看到的不是最新的代码。强制刷新CtrlF5或CmdShiftR清缓存或者给CSS文件URL加个版本号style.css?v2。第八步临时调试大法实在搞不定直接在Elements面板里改HTML结构加行内样式测试。如果行内样式生效了说明你的选择器有问题如果行内样式也不生效可能是被!important覆盖了或者是JS在定时器里不断重置样式。!-- 临时测试 --divstylecolor:red!important;测试文字/div如果这样都不行那问题可能不在CSS而在HTML结构或者JS逻辑上。几个让同事直呼内行的野路子1. 尽量少用ID选择器ID选择器权重太高用了就是给自己挖坑。一旦用了#header以后想覆盖它的样式要么用更长的ID链要么用!important要么改HTML结构。而HTML结构一改可能牵一发而动全身。除非是绝对唯一的、几乎不会变的元素比如页面根节点#app否则别用ID选样式。用类选择器给自己留条后路。2. 把权重控制在合理范围建议单个选择器的权重别超过(0,0,2,0)也就是最多两个类选择器。如果超过这个数说明你的选择器写得太复杂了该考虑重构HTML结构或者用BEM等命名规范。3. 利用层叠上下文Stacking Context降维打击有时候样式不生效不是权重问题而是层叠上下文z-index的问题。但层叠上下文的创建也跟某些CSS属性有关比如position、opacity、transform等。更绝的是层叠上下文本身也能影响样式的感知。比如在一个新的层叠上下文中某些继承的样式表现会不一样。虽然这跟权重计算关系不大但调试的时候容易混淆以为权重没生效。4. 有时候不写样式也是一种样式这句话有点禅意但意思是利用继承和默认值而不是什么都写死。比如字体、颜色这些在body或:root上定义好子元素自动继承不需要每个类都写一遍。这样代码少权重低还好维护。5. 组件样式隔离写组件的时候把样式封装好别污染全局。用CSS Modules、Styled Components、Shadow DOM或者至少加个独特的前缀比如组件名.user-card。这样即使权重低也不会跟其他组件打架。6. 写注释写注释这点最重要。如果你为了权重不得不写一个很复杂的选择器或者用了!important一定要写注释说明为什么。否则三个月后你自己来看都会想这什么鬼重构一下一重构就崩。/* 权重(0,1,2,0) 原因需要覆盖Element UI的.el-button--primary 该选择器权重(0,1,1,0)此处多一个类选择器确保优先级 修改前请确认Element UI版本是否升级 */#app .custom-btn.custom-btn--primary{background-color:#ff4d4f;}7. 善用:where()降低权重前面提过:where()这里再强调一下。它的权重永远是0适合写一些基础样式不想干扰其他权重的计算。/* 权重(0,0,0,0)不影响其他样式 */:where(h1, h2, h3){margin-top:0;line-height:1.2;}这样写相当于建议而不是命令其他样式很容易覆盖它不需要写很高的权重。8. 权重计算工具记不住权重没关系有在线工具帮你算。比如Specificity Calculator输入选择器自动算出四元组。虽然实战中不可能每次都去查但学习阶段很有用。最后唠叨两句心里话写到这儿估计你也看出来了CSS权重这东西就像手里的沙子握得越紧流失得越快。你越是想通过高权重、!important来控制样式代码就越难维护最后变成谁都不敢动的屎山。真正的高手不是能精准算出(0,1,2,0)和(0,0,3,1)哪个大的人而是根本不需要算的人。他们通过合理的架构、清晰的命名、适当的隔离让样式自然流淌各归其位根本打不起来。下次再遇到样式冲突先别急着加!important也别马上把选择器写成绕口令。深呼吸想想是不是自己的选择器写得不够体面是不是HTML结构可以优化是不是该用CSS变量是不是该把样式拆得更细。毕竟代码是写给人看的顺便给机器运行。你今天写的每一行CSS都是在给未来的自己或者接盘的同事留话。留点口德别写那种只有浏览器能看懂人类看了想骂人的代码。权重计算只是工具不是目的。咱们的目的是让页面好看让代码好维护让自己能准时下班。记住这个你就不会迷失在0,0,0,0的数字游戏里。好了就聊到这儿。下次遇到样式被覆盖知道该怎么做了吧要是还不行…那就加!important吧反正代码能跑就行谁还没个破罐子破摔的时候呢开玩笑的别真这么干。欢迎来到我的博客很高兴能够在这里和您见面希望您在这里可以感受到一份轻松愉快的氛围不仅可以获得有趣的内容和知识也可以畅所欲言、分享您的想法和见解。推荐DTcode7的博客首页。一个做过前端开发的产品经理经历过睿智产品的折磨导致脱发之后励志要翻身农奴把歌唱一边打入敌人内部一边持续提升自己为我们广大开发同胞谋福祉坚决抵制睿智产品折磨我们码农兄弟专栏系列点击解锁学习路线(点击解锁知识定位《微信小程序相关博客》持续更新中~结合微信官方原生框架、uniapp等小程序框架记录请求、封装、tabbar、UI组件的学习记录和使用技巧等《AIGC相关博客》持续更新中~AIGC、AI生产力工具的介绍例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结《HTML网站开发相关》《前端基础入门三大核心之html相关博客》前端基础入门三大核心之html板块的内容入坑前端或者辅助学习的必看知识《前端基础入门三大核心之JS相关博客》前端JS是JavaScript语言在网页开发中的应用负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客共同构建用户界面。通过操作DOM元素、响应事件、发起网络请求等JS使页面能够响应用户行为实现数据动态展示和页面流畅跳转是现代Web开发的核心《前端基础入门三大核心之CSS相关博客》介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法同时收集精美的CSS效果代码用来丰富你的web网页《canvas绘图相关博客》Canvas是HTML5中用于绘制图形的元素通过JavaScript及其提供的绘图API开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力使得前端绘图技术更加丰富和多样化《Vue实战相关博客》持续更新中~详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅《python相关博客》持续更新中~Python简洁易学的编程语言强大到足以应对各种应用场景是编程新手的理想选择也是专业人士的得力工具《sql数据库相关博客》持续更新中~SQL数据库高效管理数据的利器学会SQL轻松驾驭结构化数据解锁数据分析与挖掘的无限可能《算法系列相关博客》持续更新中~算法与数据结构学习总结通过JS来编写处理复杂有趣的算法问题提升你的技术思维《IT信息技术相关博客》持续更新中~作为信息化人员所需要掌握的底层技术涉及软件开发、网络建设、系统维护等领域的知识《信息化人员基础技能知识相关博客》无论你是开发、产品、实施、经理只要是从事信息化相关行业的人员都应该掌握这些信息化的基础知识可以不精通但是一定要了解避免日常工作中贻笑大方《信息化技能面试宝典相关博客》涉及信息化相关工作基础知识和面试技巧提升自我能力与面试通过率扩展知识面《前端开发习惯与小技巧相关博客》持续更新中~罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等《photoshop相关博客》持续更新中~基础的PS学习记录含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结日常开发办公生产【实用工具】分享相关博客》持续更新中~分享介绍各种开发中、工作中、个人生产以及学习上的工具丰富阅历给大家提供处理事情的更多角度学习了解更多的便利工具如Fiddler抓包、办公快捷键、虚拟机VMware等工具吾辈才疏学浅摹写之作恐有瑕疵。望诸君海涵赐教。望轻喷嘤嘤嘤非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益纵其简陋未及渊博亦足以略尽绵薄之力。倘若尚存阙漏敬请不吝斧正俾便精进