CSS选择器的注意点和技巧

192 阅读7分钟

1. CSS选择器的书写规范

1.1 CSS的优先级

(1)0级:通配选择器、选择符和逻辑组合伪类,此等级并不影响CSS的优先级。优先数值+0

    通配选择器:* { color: #000; }
    选择符: +、>、~
    逻辑组合伪类::not()、:is()、:where等

(2)1级:标签选择器。优先数值+1

    标签选择器:body { color: #333; }

(3)2级:类选择器、属性选择器和伪类。优先数值+10

    类选择器:.foo { color: #666; }
    属性选择器:[foo] { color: #666; }
    伪类::hover { color: #333; }

(4)3级:ID选择器。优先数值+100

    ID选择器:#foo { color: #999;}

(5)4级:style属性内联。优先数值+1000

    <span style="color: #ccc;"></span>

(6)5级:!important。优先数值+∞(建议除非重置JavaScript设置的样式外不要使用)

    .foo { color: #fff !important; }

1.2 CSS优先级计算规则

计算方式如下:

    <html lang="zh-CN">
        <body class="foo">颜色是?</body>
    </html>
    body .foo:not([dir]) { color: red; }
    html[lang] > .foo { color: blue; }

首先是body .foo:not([dir]),出现了1个标签选择器body,1个类名选择器.foo和1个否定伪类:not,以及属性选择器[dir],计算结果是 1+10+0+10 = 21。

然后是html[lang] > .foo,出现了1个标签选择器html,1个属性选择器[lang]和1个选择符>,以及一个类选择器.foo,计算结果是 1+10+0+10 =21。

两者都是21,那么最终呈现的颜色为什么颜色呢,此时就以另外一个重要的规则——“后来居上” 为准。也就是说当CSS选择器的优先级数相等时,后渲染的样式优先级更高,所以最终颜色是蓝色(blue)。

1.3 CSS的良好书写习惯

1.3.1 不要使用ID选择器

(1)优先级太高。不适合进行重置样式,会使整个项目优先级混乱,如果一定要用元素ID作为选择器标识,可使用属性选择器进行替换,如[id="csID"]。

(2)和JavaScript耦合。元素的ID主要用在JavaScript中,以方便快速获取,当ID同时和样式关联时,项目的可维护性会大打折扣。一旦ID变化,必须同时修改CSS和JavaScript。

1.3.2 不要嵌套选择器

    .box .pic .icon {}
    .nav a {}

以上CSS选择器会导致

  • 渲染性能糟糕
  • 优先级混乱
  • 样式布局脆弱
(1)渲染性能糟糕

CSS选择器的性能排序如下:

  • ID选择器
  • 类选择器
  • 标签选择器
  • 通配选择器
  • 属性选择器
  • 部分伪类,如:checked

由于CSS选择器是从右往左进行匹配渲染的(比较推荐把类选择器写在属性值匹配选择器和伪类后面),所以当你写 .box>div 时,浏览器是先匹配页面所有的< div >元素,再匹配.box。而且每加深一层嵌套,浏览器就会多一层计算。

(2)优先级混乱

选择器优先级有一个原则,那就是尽可能保持较低的优先级,这样可以方便以后进行重置一些样式。但你一旦开始嵌套,优先级规则就会变得复杂。

(3)样式布局脆弱

过多的选择器层级会限定死了HTML结构,当你日后想通过HTML调整层级或位置时非常困难。

正确的选择器用法是全部使用无嵌套的纯类名选择器。如:

    <div class="cs-box">
        <figure class="cs-box-pic">
            <img class="cs-box-pic-img" src="./img.jpg">
            <figcaption><i class="cs-box-pic-icon"></i>图片标题</figcaption>
        </figure>
    </div>
    .cs-box{}
    .cs-box-pic{}
    .cs-box-pic-img{}
    .cs-box-pic-icon{}

1.3.3 正确使用状态类名

页面的交互总是伴随着各种状态变化,比如说禁用状态、选中状态、激活状态等。当我们实现一个交互时往往是通过JavaScript和CSS共同实现,一个页面通常会有很多的交互效果,如果每个交互效果都要一个对应的类名进行控制,那JavaScript文件中控制样式的类名就会过于复杂,项目的可维护性就会变得很差。

最佳的实现方法就是使用.active、.checked等这种状态类名进行交互控制。这样我们的JavaScript代码就可以变成这样

    button.onclick = function () {
        content.className += ' active ';
    }

这样我们项目中所有的页面交互都可以使用这个状态类名进行交互控制。但同时我们也要遵循一条准则:.active状态类名自身绝对不能有CSS样式。.active类名必须自身无样式,就是一个状态标识符,用来与其他类名发生关系,让其他类名的样式发生变化,这种关系可以是父子、兄弟或者自身。比如

    <div id="content" class="cs-content">
        文字内容...
        <a href="javascript:" id="more" class="cs-content-more">更多</a>
    </div>
    .cs-content {
        height: 60px;
        line-height: 20px;
        overflow: hidden;
    }
    .cs-content.active {
        height: auto;
    }
    .active > .cs-content-more {
        display: none;
    }

这样之后设计师突然想出什么效果,直接修改CSS即可,JavaScript不需要做任何的修改。

2. CSS选择符

2.1 后代选择符空格 ( )

我们直接看例子:

    <div class="lightblue">
        <div class="darkblue">
            <p>1. 颜色是</p>
        </div>
    </div>
    <div class="darkblue">
        <div class="lightblue">
            <p>2. 颜色是</p>
        </div>
    </div>
    .lightblue { color: lightblue; }
    .darkblue { color: darkblue; }

此时两者的颜色分别是什么?

答案是1和2分别是深蓝色和浅蓝色,因为color具有继承特性,所以文字的颜色由DOM最深的赋色元素决定。

但是如果换成后代选择符呢:

    .lightblue p { color: lightblue; }
    .darkblue p { color: darkblue; }

此时两者的颜色又分别是什么呢?

正确答案是两者都是深蓝色,有点出乎意料吧,这是因为当包含后代选择符时,整个选择器的优先级与祖先元素的DOM层级没有任何关系,这是要看落地元素的优先级。在本例中,落地元素是最后的< p >元素。两个P元素彼此分离,非嵌套,DOM层级平行,再看选择器的优先级,.lightblue p 和.darkblue p 是一个类选择器(数值10)和一个标签选择器(数值1),选择器优先级相同;所以遵循“后来居上”规则,按照.darkblue p来进行渲染。

2.2 子选择符箭头(>)

子选择符和后代选择符的区别在于子选择符只会匹配第一代子元素,而后代选择符会匹配所有子元素。所以一旦使用了子选择符,元素层级关系就被强制绑定,日后修改和调整层级关系会变得麻烦,一般情况下尽量选择后代选择符。适合使用子选择符的场景通常有以下几个。

  • 状态类名控制。例如使用.active类名进行状态切换。
  • 标签受限。例如< li >标签重复嵌套。
  • 层级位置与动态判断。例如一个选择器组件在放在< body >元素下是以绝对定位浮层形式呈现。但有时需要其以静态布局嵌在某个位置,这时我们不好修改组件源码,则可以借助子选择符打个补丁:
    :not(body) > .cs-date-panel-x {
        position: relative;
    }

2.3 利用相邻兄弟选择符实现类似:first-child的效果

    <div class="cs-g">
        <p class="cs-li">列表内容1</p>
        <p class="cs-li">列表内容2</p>
        <p class="cs-li">列表内容3</p>
    </div>
    .cs-g .cs-li + .cs-li {
        color: skyblue;
    }

由于相邻兄弟选择符只能匹配后一个元素,因此第一个元素就会落空,永远不会被匹配。与:first-child的区别就在于,当容器的第一个子元素非.cs-li时,相邻兄弟选择符这个方法依然有效,但是:first-child此时却会失效。

待续。。。。