写样式的时候最怕啥?好不容易调好一个组件的颜色,结果页面其他地方莫名其妙跟着变了。BEM这套命名规矩就是为了解决这种翻车现场,但碰上:is()、:where()这些现代伪类,特异性又容易乱套。别慌,把BEM和:where()凑一块儿,能完美守住特异性的底线,顺便少写一堆冗余代码。
BEM的特异性
BEM把组件拆成块(Block)、元素(Element)、修饰符(Modifier),比如.card、.card__title、.card--featured。每个类选择器在CSS层叠里的特异性分数都是0,1,0。这就好比排队时每人拿同一个号的票,谁后到谁生效,不会出现插队加塞的情况。大型项目(比如银行网站、大学官网)里成千上万个样式,全靠这个规矩才能不互相踩踏。
| 选择器 | 特异性分数 |
|---|---|
.card | 0,1,0 |
.card__title | 0,1,0 |
.card--featured | 0,1,0 |
伪类的特异性坑
:not()、:is()这些伪类看着好用,但一不留神就会拉高特异性。举个例子:
/* 这货的特异性是 0,2,0 */
.something:not(.something--special) {
color: red;
}
.something--special {
color: blue;
}明明.something--special写在后面,结果颜色还是红的。为啥?因为:not()本身不增加特异性,但它里面的参数.something--special是个类,导致整个选择器的特异性变成了0,2,0。这就好比原本大家都是普通票,突然有个人掏出了VIP票,后面的普通票自然干不过。
:where一键清零
:where()这个伪类牛就牛在它的特异性永远是0,0,0。哪怕写成:where(button#widget.some-class),分数还是零。用它把有坑的部分包起来,就能把特异性压回去:
/* 特异性完美回到 0,1,0 */
.something:where(:not(.something--special)) {
color: red;
}
.something--special {
color: blue;
}现在.something--special排在后面,优先级正常,蓝颜色就能生效了。这操作相当于给VIP票盖了个“作废”章,大家又回到同一起跑线。
:where救场嵌套
很多人在Sass或Less里写嵌套时,为了给修饰符下的子元素单独写样式,会搞出下面这种写法:
.card { ... }
.card--featured {
.card__title { ... }
.card__img { ... }
}
.card__title { ... }
.card__img { ... }编译后.card--featured .card__title的特异性变成了0,2,0,比普通.card__title的0,1,0高。以后想覆盖就费劲了。硬核解法是给每个子元素都加修饰符类:
<div class="card card--featured">
<h3 class="card__title card__title--featured">...</h3>
<img class="card__img card__img--featured" src="...">
</div>这样写确实稳,但模板里要写一堆if判断,代码又臭又长。用:where()能偷个懒:
.card { ... }
.card--featured { ... }
.card__title {
:where(.card--featured) & { ... }
}
.card__img {
:where(.card--featured) & { ... }
}:where(.card--featured)这部分特异性为零,所以.card--featured .card__title的总特异性还是0,1,0,跟普通的.card__title完全一样。谁后出现谁说了算,不用再给每个子元素单独加修饰符类了。真香。
搞定第三方样式
有时候不得不给第三方脚本插入的DOM写样式,那些元素可能带的是ID而不是类。#widget的特异性是1,0,0,比BEM的类高出一大截。硬写ID选择器会破坏整个项目的特异性平衡。可以用:where()把ID包起来:
/* 特异性降到 0,1,0 */
.page-wrapper :where(#widget) {
background: #f0f0f0;
}但这样依赖.page-wrapper这个父类存在,万一哪天父类名改了或者没这个父元素,就尴尬了。更皮实一点的办法是结合:is():
/* 照样是 0,1,0 */
:is(.dummy-class, body) :where(#widget) {
background: #f0f0f0;
}:is(.dummy-class, body)会取参数里最高的特异性——.dummy-class是0,1,0,body是0,0,1,所以整体拿到0,1,0。再加上:where(#widget)的零分,最终选择器的特异性还是0,1,0。.dummy-class这个类压根不用在HTML里存在,纯粹是个工具人,用来抬一手特异性分数。body标签永远在那儿,保证选择器能命中目标。这招虽然有点骚,但在处理脏数据的时候特别管用。
