CSS 选择器和优先级权重计算这么简单,你还没掌握?一篇文章让你轻松通关面试!(上)

179 阅读11分钟

引言

许多小伙伴即将面临找工作,正在为面试忙碌准备,但在日常开发中,常因依赖框架而逐渐忽略了一些基础知识,比如——CSS 选择器以及其优先级权重,你还记得吗? 如果你也遇到过样式冲突而找不到原因,或者面试时被问到选择器优先级的计算而手忙脚乱,那么这篇文章正是为你而写!

接下来,我们将从基础规则讲起,带你逐步了解CSS选择器的概念、掌握 CSS 选择器的优先级权重计算,轻松应对日常开发和技术面试!

本篇文章将主要讲述CSS选择器。由于篇幅限制,如果是想看关于CSS选择器的优先级权重计算内容的小伙伴,请移步我的另外一篇文章:

CSS 选择器和优先级权重计算这么简单,你还没掌握?一篇文章让你轻松通关面试!(下)

正文

CSS选择器

什么是CSS选择器?

CSS 选择器规定了 CSS 规则会被应用到哪些元素上。如果你想要将一些特定的样式添加给html页面中的某些元素,那就需要CSS选择器来帮忙指向要作用的标签

以下是CSS选择器的分类:

  • 基本选择器
    1. 标签(元素)选择器
    2. 类选择器
    3. id选择器
    4. 通配符选择器
  • 复合选择器
    1. 交集(并列)选择器
    2. 并集选择器
    3. 兄弟选择器
    4. 属性选择器
    5. 后代选择器
    6. 伪类选择器
    7. 伪元素选择器

基本选择器已经能够帮助我们选中所有的选择器,但复合选择器在基本选择器的基础上做了很多优化。


基本选择器

1. 标签(元素)选择器

也许你会注意到,这个选择器有两个名字,这是因为通常我们习惯于称它为标签选择器,但MDN官方文档中把它叫做元素(element)选择器

<style>
    p{
        color: red;
    }
</style>
<body>
    <h1>标题元素</h1>
    <p>段落元素1</p>
    <p>段落元素2</p>
    <span>span元素</span>
    <div>div元素</div>
</body>
  • 语法tagName{}
  • 优点:简单易懂。一眼就能知道把<p></p>中的内容的颜色改成了红色。
  • 缺点:无法区分同样标签的样式。比如在这里有两个<p></p>,如果仅仅使用标签选择器,就无法做到改变段落元素1的样式。
2. 类选择器
<style>
    .xxx{
        color: blue;
    }
</style>
<body>
    <h1>标题元素</h1>
    <p>段落元素</p>
    <p class="xxx">段落元素</p>
    <span class="xxx">span元素</span>
    <div>div元素</div>
</body>
  • 语法:首先给项目中的一个标签加上属性:例如<p class="xxx"></p>,再用.xxx(类名){}
  • 优点:可以跨标签名分类。比如这段代码中,由于添加了属性,既可以改变<p></p>中的元素,又可以改变<span></span>中的元素。
3. id选择器
  • 语法:首先给项目中的一个标签起一个独一无二的id,例如<p id="xx"></p>,再用#xx(id名){}

细心的小伙伴已经发现了,这个语法和类选择器非常像,是的没错,他们的主要区别就在于id选择器中为标签起的id名是唯一的,这也意味着id选择器只能指向唯一的标签

4. 通配符选择器
  • 语法:它和以上都不太一样,通配符选择器不需要标签名,也不用通过任何的属性,一个*{}就能掌握全局。所以它一般用于页面的初始化

复合选择器

1. 交集(并列)选择器

从名字就大概能猜到了,这个选择器一看就和我们高中就学过的交并集中的交集有关。多说无益,来看下面的代码吧:

<style>
p.person{
    color: blue;
}
</style>
<body>
    <p class="person">张三</p>
    <div class="person">李四</div>
    <p class="animal">小羊苏西</p>
    <div class="animal">黑猫警长</div>
</body>

在代码中我们可以看到,如果我想选中黑猫警长,直接用标签选择器会一起选中李四,用类选择器又会选中小羊苏西,这可怎么办呢?

这个时候就需要我们的交集选择器出场啦,我们可以使用它来选中标签是p且类为animal的元素。

  • 语法xx(标签名/类名/id名)xxx(标签名/类名/id名){},要注意,这两个属性是紧紧贴在一起的,中间没有空格。
2. 并集选择器

同样的,听名字也听得出来,肯定和并集差不多,事实也确实如此。我们拿上面的代码举例:

<style>
p,
div{
    color: blue;
}
</style>
<body>
    <p class="person">张三</p>
    <div class="person">李四</div>
    <p class="animal">小羊苏西</p>
    <div class="animal">黑猫警长</div>
</body>

这个时候我如果想选择所有标签为p或者div里的元素,我就可以使用并集选择器

  • 语法:xxx(标签名),xx(标签名){}
3. 兄弟选择器

是兄弟,但也分亲兄弟和表兄弟。兄弟选择器也分选择一个兄弟还是所有兄弟。

注意!兄弟之间的地位是平等的,只有并列关系同等级别,才能叫做兄弟,如果有嵌套关系或其他非并列关系,那就不能算是兄弟,也就无法选中。

我们来看下面这段代码:

<body>
    <h1>大标题</h1>
    <p>副标题</p>
    <p>段落元素</p>
    <span>一个元素</span>
    <div>不知道起什么名字了元素</div>
    <div class="div">div元素</div>
    <p>p元素</p>
</body>

(1) 通过+找到身后紧邻的一个兄弟

如果这时我想要选中副标题这个元素,如果直接用标签选择器显然是不现实的,会一起选中段落元素,所以这时我可以使用兄弟选择器,选择h1紧邻的一个p标签中的元素。

<style>
    h1+p{
        color: aliceblue;
    }
</style>
  • 语法xxx(标签名/类名/id名)+xx(标签名/类名/id名){}

(2) 通过~找到身后紧邻的所有兄弟

有时我们会不止想选中一个兄弟,有福得大家一起享,有活得大家一起干。这时可以用 ~ 选择符。一举选中 h1 后面所有的 p 元素兄弟。

你可能会好奇:最下面的 ph1 并不紧邻,为什么也被选中了?

这是因为 ~ 会匹配 h1 后所有的兄弟标签,再从中筛选出符合条件的 p 元素。因此,h1 后的所有 p 都会被选中,无论是否紧邻。

<style>
    h1~p{
        color: aliceblue;
    }
</style>
  • 语法xxx(标签名/类名/id名)~xx(标签名/类名/id名**){}
4. 属性选择器

属性选择器是一种通过元素属性的值来选中元素的选择器,根据不同的需求可以分为以下几种情况:

  • 选中带有某个属性的元素:[属性名]{}
  • 选中属性值完全匹配的元素:[属性名=属性值]{}
  • 选中属性值以某个字符串开头的元素:[属性名^=值]{}
  • 选中属性值以某个字符串结尾的元素:[属性名$=值]{}
  • 选中属性值包含某个字符串的元素:[属性名*=值]{}
  • 选中属性值包含某个独立单词的元素:[属性名~=值]{}

(1) 带某种属性

如果你想选中所有带有 class 属性的元素,就可以使用 [class] ,一下就能选中带有class<p><span>:

<style>
    [class] {
        color: blue;
    }
</style>
<body>
    <h1>标题</h1>
    <p class="highlight">这是一个段落</p>
    <p>这是另一个段落</p>
    <span class="highlight">这是一个 span 元素</span>
    <div data-type="info">这是一个 div 元素</div>
    <div data-type="error">这是另一个 div 元素</div>
</body>
  • 语法[属性名]{}

(2) 属性值完全匹配

如果需要精确匹配属性值,比如选中 class="highlight" 的元素,可以使用 [class="highlight"]

<style>
    [class="highlight"] {
        font-weight: bold;
    }
</style>
<body>
    <p class="highlight">这是一个段落</p>
    <p>这是另一个段落</p>
    <span class="highlight">这是一个 span 元素</span>
    <div data-type="info">这是一个 div 元素</div>
</body>
  • 语法[属性名=属性值]{}

(3) 属性值以某个字符串开头
想选中所有 data-type 属性值以 info 开头的元素,就可以用 [data-type^="info"]

<style>
    [data-type^="info"] {
        background-color: lightgreen;
    }
</style>
<body>
    <div data-type="info">这是一个 div 元素</div>
    <div data-type="error">这是另一个 div 元素</div>
</body>
  • 语法[属性名^=值]{}

(4) 属性值以某个字符串结尾

如果想选中 data-type 属性值以 error 结尾的元素,可以使用 [data-type$="error"]

<style>
    [data-type$="error"] {
        color: red;
    }
</style>
<body>
    <h1>标题</h1>
    <p class="highlight">这是一个段落</p>
    <div data-type="info">这是一个 div 元素</div>
    <div data-type="error">这是另一个 div 元素</div>
</body>
  • 语法[属性名$=值]{}

(5) 属性值包含某个字符串

有时候我们需要判断属性值是否包含某些字符,比如选中所有 data-type 属性值中带有 type 的元素,就可以使用 [data-type*="type"]

<style>
    [data-type*="type"] {
        border: 1px dashed gray;
    }
</style>
<body>
    <p class="highlight">这是一个段落</p>
    <span class="highlight">这是一个 span 元素</span>
    <div data-type="info">这是一个 div 元素</div>
    <div data-type="error">这是另一个 div 元素</div>
</body>

(6) 属性值包含某个独立单词

这和“(5) 属性值包含某个字符串”有点相似,但二者存在差别:~= 会将属性值视为一组由空格分隔的单词,仅匹配独立的单词,而 *= 则会匹配属性值中任何位置出现的指定的字符串,无论是否是独立单词。

比如需要选中 class 属性值中包含 highlight 这个独立单词的元素,就可以使用 [class~="highlight"],如此就只会选中第一项,而不会选中第二项:

<style>
    [class~="highlight"] {
        background-color: yellow;
    }
</style>
<body>
    <ul>
        <li class="item highlight">第一项</li>
        <li class="item">第二项</li>
        <li class="itemhighlight">第三项</li>
        <li class="high">第四项</li>
    </ul>
</body>
  • 语法[属性名~=值]{}
5. 后代选择器

后代选择器可以选择一个元素的儿子、孙子、重孙子... ...子子孙孙世世代代无穷尽也!分为选直接后代和所有后代。

(1) 直接后代

可以选中某元素的直接子元素,也就是儿子。只匹配紧邻一级的后代,而不会匹配更深层的嵌套元素。

比如在这里就只会选中.div 的直接子元素中 class="p"div子代p1

<style>
    .div > .p {
        color: aqua;
    }
</style>
<body>
    <div class="div">
        <h1>div子代h1</h1>
        <p class="p">div子代p1</p>
        <p>div子代p2</p>
        <div>
            <span>div中的div中的span</span>
            <p>div中的div中的p</p>
        </div>
        <div>div中的div</div>
    </div>
</body>
  • 语法xxx(标签名/类名/id名)>xxx(标签名/类名/id名)

(2) 所有后代

可以选中某元素的所有后代,包括儿子、孙子、重孙... ...

比如这里,就可以选中 .div 的所有后代中 class="p"<p>,也就是 div子代p1div中的div中的p

<style>
    .div .p {
        color: aqua;
    }
</style>
<body>
    <div class="div">
        <h1>div子代h1</h1>
        <p class="p">div子代p1</p>
        <p>div子代p2</p>
        <div>
            <span>div中的div中的span</span>
            <p>div中的div中的p</p>
        </div>
        <div>div中的div</div>
    </div>
</body>
  • 语法xxx(标签名/类名/id名) xxx(标签名/类名/id名)

这里中间有空格的噢!不要忽略了!

6. 伪类选择器(不是类,很像类)

伪类选择器选中的不是元素,而是元素的某种状态描述,这正好对应了伪类中两个细致的小分支:动态伪类结构伪类

  • 语法:通常在用基本选择器选中元素后,在后通过:xxx(伪类名)的形式添加伪类。

动态伪类中一共有四种不同的状态:

1. link:没有被访问过。

2. visited:被访问过的。

3. hover:鼠标悬停的状态。

4. active:被激活的状态,也就是用鼠标按住不放的状态。

linkvisited只有a链接才有。

<style>
    a:link {
        color: blue;
    }
    a:visited {
        color: purple;
    }
    a:hover {
        text-decoration: underline;
    }
    a:active {
        color: red;
    }
</style>
<body>
    <a href="2.html" target="_blank">我是一个a链接</a>
</body>
  • 关于顺序的注意事项:动态伪类之间的顺序很重要,如果你要同时写这几个伪元素的话,建议按照 :link:visited:hover:active的顺序。这样可以确保伪类效果不被覆盖。例如,如果将 :hover 写在 :link:visited 前面,鼠标悬停时颜色可能会失效,因为后面的规则会覆盖之前的样式。

结构伪类种类就很多了,由于在日常开发中并不常见,所以不赘述,在这里给大家大致列出:

  1. first-child:选中父元素的第一个子元素。

  2. last-child:选中父元素的最后一个子元素。

  3. nth-child(n):选中父元素的第 n 个子元素,n 可以是以下形式:

    • 从 0 开始,理论上可到正无穷。
    • 超出范围(如 0 或大于元素数量)则无法匹配。
    • 如果直接写 n,则会匹配所有子元素。
    • 支持公式表示,如:
      • 2neven:匹配偶数子元素。
      • 2n+1odd:匹配奇数子元素。
      • -n+5:匹配前 5 个子元素。
      • 倒序通过计算公式反推出。
  4. first-of-type:选中父元素中特定类型的第一个子元素。

  5. last-of-type: 选中父元素中特定类型的最后一个子元素。

  6. nth-of-type(n): 选中父元素中特定类型的第 n 个子元素。

  7. nth-last-child(n): 选中父元素从后往前数的第 n 个子元素。

  8. nth-last-of-type(n): 选中父元素中特定类型从后往前数的第 n 个子元素。

  9. only-child: 选中父元素中唯一的子元素。

  10. only-of-type: 选中父元素中特定类型的唯一子元素。

  11. root: 选中文档的根元素,通常为 <html>

  12. empty: 选中没有任何子节点(包括文本节点)的元素。


UI伪类

  1. checked: 选中被点击的单选按钮或复选框。
  2. disabled: 选中被禁用的元素(如禁用的输入框)。
  3. enabled: 选中可用的元素(如未禁用的输入框)。
7. 伪元素选择器(不是元素,很像元素)
  • 语法:和伪类选择器一样,伪元素选择器选中的当然也不是元素,你可以把它理解为是某些元素的某些位置。通过::(伪类名)实现。常用的伪元素有::before::after::first-letter::first-line

(1) ::before::after:在元素之前或之后插入内容

假设我们需要在商品价格前加上美元符号 $,或者在书籍名称前后加上书名号 《》,就可以使用伪元素 ::before::after 实现,这个时候可以发现当我们鼠标去选中它们时,无法选中美元符号和书名号,只能选中里面的文字:

<style>
    span::before {
        content: "$";
    }
    span.book::before {
        content: "《";
    }
    span.book::after {
        content: "》";
    }
</style>
<body>
    <span>999.9</span>
    <span class="book">论语</span>
</body>
  • 语法::before在元素内容前插入内容;::after:在元素内容后插入内容。

(2) ::first-letter:选择元素的首字母

有时候为了美观,段落的首字母需要设置为大号字体,可以使用 ::first-letter

<style>
    p::first-letter {
        font-size: 20px;
        color: red;
    }
</style>
<body>
    <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis, atque, labore inventore rerum quae ullam asperiores obcaecati placeat nisi quisquam tempora eveniet, error nihil doloremque ea architecto aperiam id ipsum.</p>
</body>

(3) ::first-line:选择元素的第一行文本

如果需要给段落的第一行文本设置什么样式,可以使用 ::first-line。它会选中段落的第一行文字,无论内容如何折行。这里就不再举例了,和上面 ::first-letter是一样的。

结语

由于篇幅限制,本篇文章暂时写到这里,如果感兴趣,可以进入我的主页,在下篇文章和我一起继续逐步深入掌握CSS选择器的优先级权重计算!

如果你在阅读过程中发现任何问题或有其他的想法,欢迎交流探讨!也祝正在准备面试的小伙伴们都能拿到自己心仪的 offer,加油!🎉