边界元。 就像前端开发世界中看似所有的技术一样, 以 BEM 格式编写 CSS 可以偏光。 但它是——至少在我的 Twitter 泡沫中——更受欢迎的 CSS 方法论之一。
就个人而言,我认为 BEM 很好,我认为你应该使用它。 但我也明白为什么你可能不这样做。
不管您对 BEM 的看法如何,它都提供了几个好处,最大的好处是它有助于避免 CSS 级联中的特异性冲突。 这是因为,如果使用得当,任何以 BEM 格式编写的选择器都应该具有相同的特异性分数(0,1,0
). 多年来,我为许多大型网站(想想政府、大学和银行)设计了 CSS,正是在这些大型项目中,我发现 BEM 真正发挥了作用。 当您确信您正在编写或编辑的样式不会影响网站的其他部分时,编写 CSS 会更有趣。
实际上有一些例外情况,在这些情况下添加特异性被认为是完全可以接受的。 例如: :hover
和 :focus
伪类。 那些的特异性得分为 0,2,0
. 另一个是伪元素——比如 ::before
和 ::after
- 其特异性得分为 0,1,1
. 不过,对于本文的其余部分,我们假设我们不希望任何其他特异性蠕变。 🤓
但我并不是真的要向您推销 BEM。 相反,我想谈谈我们如何将它与现代 CSS 选择器一起使用——想想 :is()
, :has()
, :where()
, 等等 - 获得甚至 更多 控制 级联.
现代 CSS 选择器是怎么回事?
CSS 选择器 4 级规范 为我们提供了一些强大的新方法来选择元素。 我最喜欢的一些包括 :is()
, :where()
及 :not()
,每一个都得到所有现代浏览器的支持,并且现在几乎可以安全地用于任何项目。
:is()
和 :where()
基本上是同一件事,除了它们如何影响特异性。 具体来说, :where()
总是有一个特异性得分 0,0,0
. 是的,甚至 :where(button#widget.some-class)
没有特异性。 同时,特异性 :is()
是其参数列表中具有最高特异性的元素。 因此,我们已经在可以使用的两个现代选择器之间进行了 Cascade-wrangling 区分。
令人难以置信的强大 :has()
关系伪类也是 迅速获得浏览器支持 (并且是自 CSS 以来最大的新特性 格, 在我的愚见)。 但是,在撰写本文时,浏览器支持 :has()
还不够好,不能用于生产。
让我在我的 BEM 中添加一个伪类,然后……
/* ❌ specificity score: 0,2,0 */
.something:not(.something--special) {
/* styles for all somethings, except for the special somethings */
}
哎呀! 看到那个特异性分数了吗? 请记住,对于 BEM,我们理想情况下希望我们的选择器都具有 0,1,0
. 为什么是 0,2,0
坏的? 考虑同样的例子,展开:
.something:not(.something--special) {
color: red;
}
.something--special {
color: blue;
}
即使第二个选择器在源顺序中排在最后,第一个选择器的更高特异性(0,2,0
) 获胜,颜色为 .something--special
元素将被设置为 red
. 也就是说,假设您的 BEM 编写正确并且所选元素同时具有 .something
基类和 .something--special
在 HTML 中应用于它的修饰符类。
如果使用不当,这些伪类会以意想不到的方式影响 Cascade。 正是这些类型的不一致会导致令人头疼的问题,尤其是在更大、更复杂的代码库上。
该死。 那么现在怎么办?
记住我在说什么 :where()
以及它的特异性为零的事实? 我们可以利用它来发挥我们的优势:
/* ✅ specificity score: 0,1,0 */
.something:where(:not(.something--special)) {
/* etc. */
}
此选择器的第一部分 (.something
) 得到其通常的特异性得分 0,1,0
。但是品牌对其自身难以衡量的部分,无法做出有效提升 :where()
- 以及其中的一切 - 具有特殊性 0
,这不会进一步增加选择器的特异性。
:where()
允许我们嵌套
像我一样不太关心特异性的人(公平地说,可能有很多人)在嵌套方面做得很好。 通过一些无忧无虑的键盘敲击,我们可能会得到这样的 CSS(请注意,为简洁起见,我使用 Sass):
.card { ... }
.card--featured {
/* etc. */
.card__title { ... }
.card__title { ... }
}
.card__title { ... }
.card__img { ... }
在这个例子中,我们有一个 .card
零件。 当它是“特色”卡片时(使用 .card--featured
类),卡片的标题和图像需要采用不同的样式。 但是,正如我们 现在 知道,上面的代码导致与我们系统的其余部分不一致的特异性分数。
顽固的特异性书呆子可能会这样做:
.card { ... }
.card--featured { ... }
.card__title { ... }
.card__title--featured { ... }
.card__img { ... }
.card__img--featured { ... }
那还不错,对吧? 坦率地说,这是漂亮的 CSS。
但是 HTML 有一个缺点。 经验丰富的 BEM 作者可能痛苦地意识到有条件地将修饰符类应用于多个元素所需的笨重模板逻辑。 在此示例中,HTML 模板需要有条件地添加 --featured
三个元素的修饰类(.card
, .card__title
及 .card__img
) 尽管在现实世界的例子中可能更多。 这是很多 if
声明。
:where()
selector 可以帮助我们编写更少的模板逻辑——以及更少的引导 BEM 类——而不会增加特定级别。
.card { ... }
.card--featured { ... }
.card__title { ... }
:where(.card--featured) .card__title { ... }
.card__img { ... }
:where(.card--featured) .card__img { ... }
这是同样的事情,但在 Sass 中(注意尾随 &符号):
.card { ... }
.card--featured { ... }
.card__title {
/* etc. */
:where(.card--featured) & { ... }
}
.card__img {
/* etc. */
:where(.card--featured) & { ... }
}
您是否应该选择这种方法而不是将修饰符类应用于各种子元素是个人喜好的问题。 但至少 :where()
现在给我们选择!
非 BEM HTML 呢?
我们并不生活在一个完美的世界中。 有时您需要处理您无法控制的 HTML。 例如,注入您需要设置样式的 HTML 的第三方脚本。 该标记通常不是用 BEM 类名编写的。 在某些情况下,这些样式根本不使用类,而是使用 ID!
再次, :where()
有我们的支持。 这个解决方案有点 hacky,因为我们需要引用我们知道存在的 DOM 树更上层某处的元素的类。
/* ❌ specificity score: 1,0,0 */
#widget {
/* etc. */
}
/* ✅ specificity score: 0,1,0 */
.page-wrapper :where(#widget) {
/* etc. */
}
引用父元素感觉有点冒险和限制。 如果该父类发生更改或由于某种原因不存在怎么办? 一个更好的(但也许同样hacky)解决方案是使用 :is()
反而。 记住,特殊性 :is()
等于其选择器列表中最具体的选择器。
因此,不是引用我们知道(或希望!)存在的类 :where()
,如上例所示,我们可以引用一个组成的类和 标签。
/* ✅ specificity score: 0,1,0 */
:is(.dummy-class, body) :where(#widget) {
/* etc. */
}
永远存在的 body
将帮助我们选择我们的 #widget
元素,以及存在的 .dummy-class
类里面一样 :is()
给出了 body
选择器与类具有相同的特异性分数(0,1,0
)...以及使用 :where()
确保选择器不会比这更具体。
而已!
这就是我们如何利用 :is()
和 :where()
伪类以及我们在以 BEM 格式编写 CSS 时获得的特异性冲突预防。 而在不久的将来, 一旦 :has()
获得 Firefox 支持 (在撰写本文时,它目前在标志后面得到支持)我们可能希望将它与 :where() 配对以取消其特异性。
无论您是否全神贯注于 BEM 命名,我希望我们能同意在选择器特异性方面保持一致是一件好事!