谈一谈CSS选择器择优及BEM

很久没瞎扯了,看着今天下雨也没地方去,闲下来撸一篇吧。

关于CSS,其实只要不用奇葩表达式、书写不会太离谱,对页面性能影响是相对较小,毕竟现代浏览器的渲染能力还是可以的。但除了性能问题,CSS的书写应该注意后期维护和拓展能力。

在项目开发中,对于同样一个视图,不同开发者的代码实现及写法可能会不一样,对问题的处理方式也有可能不一致,所以在同一个项目组里面,如果没有一个大致相同的规范来限定CSS的书写方式,后期维护五花八门的CSS将变得很费劲。

今天要扯的就是CSS选择器。选择器是CSS学习中的一个重要部分,可以说掌握了选择器,CSS最难的部分算是搞定了,剩下的就都是些属性了,记一记就好了。

先介绍一下主要CSS选择器(按性能从高到低)

ID 选择器,例如#fleming

传说中性能最好的选择器,没有之一。其实在现代浏览器中,ID选择器的性能与类选择器的性能相差无几。ID选择器在选择器名称前带#,示例:

HTML:

<div id="fleming">弗斯基</div>

CSS:

#fleming{
    font-weight:bold;
}

类选择器,例如.fleming

类选择器是CSS中最常用的选择器,可以选中class相同的所有元素。与ID选择器有所不同,ID选择器是选中某个ID的元素,而类选择器是选中class相同的所有元素。例如:

HTML

<div class="fleming">弗斯基</div>
<p class="fleming">厨子</p>
<span class="fleming">有点胖</span>

CSS

.fleming{
    color: red;
}

元素选择器,例如div

元素选择器通常将是某个 HTML 元素,比如 p、h1、em、a,甚至可以是 html 本身当做选择器名称。

HTML

<div>弗斯基</div>
<p>厨子</p>
<span>有点胖</span>

CSS

div, p, span{
    color: red;
}

兄弟选择器,例如 h2 + p

如果需要选择紧接在另一个元素后的元素,而且二者有相同的父元素,可以使用相邻兄弟选择器(Adjacent sibling selector)。例如,如果要增加紧接在 h1 元素后出现的段落的上边距,可以这样写:

HTML

<h1>弗斯基</h1>
<p>老司机</p>

CSS

h1 + p{
    margin: 10px;
}

子选择器,例如li > ul

如果想只选择某个元素的子元素,请使用子元素选择器(Child selector)。 例如,如果希望选择只作为 h1 元素子元素的 strong 元素,可以这样写:

HTML

<h1>This is <strong>very</strong> <strong>very</strong> important.</h1>

CSS

h1 > strong {
    color:red;
}

后代选择器,例如div span

与子选择器有些区别,后代选择器可以选中包括子元素及子元素的子元素。 比子元素选择器范围更广。例如希望对 div 元素中的 <span>元素应用样式,可以这样写:

HTML

<div>
    This is <span>apple</span>
    <p>This is <span>orange</p></p>
</div>

CSS

div p{
    color: red;
}

通用选择器,例如 *

通用选择器可能是所有选择器中最强大的,却使用最少的。通用选择器的作用就像是通配符,它匹配所有可用元素。通用选择器由一个星号表示。通用选择器一般用来对象页面上所有元素应用样式。

HTML

<div>Fleming</div>
<p>Test</p>
<ul>
    <li>厨子</li>
</ul>

CSS

*{
    color: red;
}

属性选择器,例如 type = “text”

可以选中所有包含某种属性的元素。例如:

HTML

<input type="text">

CSS

[type="text"]{
    color: red;
}

伪类/伪元素选择器,例如 a:hover

可为一些元素添加不同状态的样式,可以使用伪类选择器。例如给a元素添加鼠标hover样式。

HTML

<a href="https://chenfengming.cn">弗斯基</a>

CSS

a:hover{
    color: red;
}

除此之外,选择器可以组合使用,例如: .info p > a,但需要注意的一点是,浏览器解析选择器是从右到左的,复杂的选择器组合会耗费较多的时间去解析,对页面整体性能会有影响。例如选择器 .info p > a,浏览器查找到所有a元素之后还需要继续匹配父级元素是p 的a元素,查找完成之后再继续匹配属于class是.info的元素子级元素。

总的来说,CSS选择器的使用不仅仅要考虑到选择器的性能,需要结合当前项目特点,选择合适的选择器,兼顾页面性能和后期的维护和拓展。例如,不能因为ID选择器的高性能就觉得整页都是用ID选择器,那样CSS维护会变得非常困难,不利于组件化开发。适当的组合使用选择器是允许的,但不要过度嵌套选择器。

关于CSS BEM

上面有提到选择器的书写对页面CSS渲染性能有一定的影响,那么如何更加规范、合理的书写选择器能既能方便理解用意又能便于后期维护和拓展呢?

BEM is a highly useful, powerful, and simple naming convention that makes your front-end code easier to read and understand, easier to work with, easier to scale, more robust and explicit, and a lot more strict.

先介绍一下BEM, Block(块) Element(元素) Modifier(修饰器)的简称。使用BEM规范来命名CSS,组织HTML中选择器的结构,利于CSS代码的维护,使得代码结构更清晰,缺点主要是选择器名会比较丑陋。此外,BEM规范的遵循在一定程度上能帮助优化CSS选择器结构,从而提升页面性能。

在BEM规范中,以双下划线 __来作为块和元素的间隔,以单下划线_来作为块和修饰器或元素和修饰器的间隔,以中划线 – 来作为块|元素|修饰器名称中多个单词的间隔。保证各个部分只有一级 B__E_M ,修饰器需要和对应的块或元素一起使用,避免单独使用。

举个例子

HTML

<form class="form form--theme-xmas form--simple">
    <input class="form__input" type="text" />
    <input class="form__submit form__submit--disabled" type="submit" />
</form>

CSS

.form { }
.form--theme-xmas { } //某种状态的form
.form--simple { } //某种状态的form
.form__input { } // Form子元素input
.form__submit { } // Form子元素summit
.form__submit--disabled { } // Form子元素submit带disabled状态

LESS(CSS预处理器)

.form {
    &--theme-xmas { } //某种状态的form
    &--simple { } //某种状态的form
    &__input { } // Form子元素input
    &__submit {
       &--disabled { } // Form子元素submit带disabled状态
    } // Form子元素summit
}

这样书写的好处是编译出来的CSS只有一层,不会有多个选择器嵌套的情况,浏览器执行起来效率会高些,另外,因为嵌套层数少,样式优先级比较低,后期做响应式或其它样式覆盖会比较容易。

具体使用需要结合项目特点。对于BEM中CSS选择器的命名,不宜过长,适当、能表达意思即可。关于BEM想了解更多的童鞋参见:http://getbem.com/

另外,关于静态资源与页面性能。在现代浏览器中,资源是并发加载的(同域名下约6个左右),当资源过多时,资源总数大于并发数,后续的资源加载会受限于浏览器并发数。许多网站为静态资源提供多个域名调用方式从而提升浏览器静态资源加载效率。但即便如此,浏览器仍然有可能出现资源阻塞的情况。例如,CSS资源就会阻塞浏览器渲染进程,也就是说在这之前,浏览器不会渲染任何已处理资源,直到CSSOM(CSS Object Model)构建完毕。当CSS资源阻塞时,DOM节点的渲染以及JavaScript处理会延时。可见低效率的CSS会影响到整个页面的性能。

当浏览器遇到 script 标签时,DOM 构建将暂停,直至脚本完成执行。 所以,一般在页面引用CSS会放置在页面header部分,JavaScript会被放置带页面末尾,这样做的目的是在确保页面样式不会受到JavaScript的影响下优先渲染。开始扯远了,今天就先说到这了。