李涛自留地
回到顶部
首页 > 前端 > css选择器优先级高低排列,我把权重算错那次改回来了

css选择器优先级高低排列,我把权重算错那次改回来了

文章目录

侧栏那条链接的颜色,前后改了四遍都没改对。问题不在哪条规则写错了,而在同一个元素被几条规则同时盯上时谁说了算,也就是css选择器优先级高低排列怎么算。这张比对表算错一次,后面全歪。

css选择器权重计算是四列并排,不是十进位

把权重写成一个数字是错的。浏览器比的是四列:行内样式、ID、class(属性选择器和伪类也算这一列)、元素与伪元素。从左往右逐列比,哪一列先分出大小就停,后面几列不用看。

四列权重从行内样式一直排到元素选择器

.nav .item 是 (0,0,2,0),#sidebar .item 是 (0,1,1,0)。第二列先冒出一个 1,第三列多一个 class 也追不回来。写十一个 class 是 (0,0,11,0),碰上 (0,1,0,0) 照样输——四列不是十进制,别攒够十个 class 去换一个 id。

行内样式单独占一列,排在第一列。一条 style="color:red" 能压过任何不带 !important 的选择器,这跟选择器写得多长没关系。

现象:被划掉的那一行

侧栏链接的颜色一直改不动。Styles 面板里 .nav .item 那一条横着一道删除线,紧挨着的 #sidebar .item 是唯一没被划掉的;Computed 面板点开 color,显示 rgb(204, 0, 0),右侧标的来源正是 #sidebar .item。

控制台可以复核同一件事:

const el = document.querySelector('.nav .item');
getComputedStyle(el).color;   // "rgb(204, 0, 0)"

getComputedStyle 拿到的是层叠算完的结果,不是某一条规则写的值。它返回红,说明赢下来的是红的那条,跟源码里谁写在后面没关系。

我加的覆盖规则越写越长,.page .main .list li a 已经四层,还是没赢。层数不加分,加的是第三列里 class 的个数。

更麻烦的是同一条 .nav .item 在三个页面里分别被不同的 id 压住,改一处只修好一处。

侧栏这条链接的颜色,最后是由css选择器优先级高低排列里第二列决定的。

原理::not() 括号里的东西照算

四列只解决大部分情况。css选择器优先级高低排列剩下三处容易算错,一处比一处隐蔽。

四个步骤一路往下,第三步分出一条支线

第一处是 :not()。不少人当它只是个筛选条件,不参与权重,其实括号里的选择器照算::not(.x) 加一个 class,:not(#fake) 实打实加一个 ID。我写过 .list a:not(#no-such-id),本意是排掉一个根本不存在的 id,结果这条规则被抬到 ID 级别,压住了三条本该生效的样式。

第二处是 :where()。整条权重归零,专门用来写那些准备被人覆盖的默认值。:is() 反过来,取括号里最具体的那一个。两个写法解决的是同一类问题:要让这条规则容易被盖住,还是让它的权重跟着内容走。

还有一处是归类。[data-role] 这种属性选择器、:nth-child(2) 这种伪类,都记在第三列,各算一个;::before 这种伪元素记在第四列,也只算一个。归类记错,四列的账照样算错。

做法:先把 id 从样式表里清出去

第一步,数一遍 !important 有多少处。在项目根目录跑:

grep -rn "!important" src/styles | wc -l

输出是 37。三十七处里有二十一处挤在两个文件,基本是几轮互相覆盖留下的补丁。

第二步,把 id 选择器降级。#sidebar .item 改成 .sidebar .item,权重从 (0,1,1,0) 掉到 (0,0,2,0),跟 .nav .item 平级,这时谁在后面谁赢,顺序看得见也好查。降级之后,这套排序规则至少能一眼读明白。

第三步,需要留给别人覆盖的默认样式,外面套一层 :where(),对方不用提高权重就能盖住。

第四步,改完在 Computed 面板逐个核对颜色,别只看源码里改没改对。

左右两盘并列,层序箭头方向正好相反

改之前把旧的编译产物另存了一份。改完拿两个页面对拍,能立刻看出哪一页被我改坏了,而不是等上线后被人指出来。

这一步的代价是:改完有三处旧样式立刻失效,得挨个补。补的办法是把原来靠 id 压下去的那几条规则挪到后面,不是再加一层 !important。三处补完,样式表反而短了几行。

!important 的优先级和 @layer 的边界

!important 在层叠顺序里比权重更靠前:一条带 !important 的 class 规则,能压过不带 !important 的行内样式。两边都带着 !important 时,才回到四列去比——但行内样式上的 !important 是个例外,它压过任何选择器写出来的 !important,不会退回四列。

@layer 是另一套规则。分层的样式整体输给没分层的,跟权重无关;声明加上 !important 之后顺序又反过来,越靠前的层越优先。这两条叠在一起,权重表就不够用了。

权重表之外还有几处管不到:继承来的值不参与比较,@media 里的规则也不因为多包一层就加分,:hover 这类状态选择器只按伪类的身份算一次。

继承这条值得单说。子元素上没写 color,父元素那条规则再强,也只是把值传下来,它没在这层参与竞争。同权重的规则才比先后,后写的赢,所以把规则挪到后面能当临时补丁用,代价是下一次改动又得重排一遍。

边界就是这些。能靠调顺序、降低选择器强度解决的,不要动 !important;真要压过第三方组件或者行内样式,再把它当最后一道。用 id 提权重这件事,除了锚点跳转,样式里没有非用不可的场合。这条在css选择器优先级高低排列里算常识,可每次动老样式表都得重新确认一遍。

理顺权重花掉的那半天,比之后每次改颜色都靠 !important 试错便宜得多。

若非作者标注皆为李涛自留地原创文章,转载或复制请以超链接形式并注明出处。