CSS【选择器】万字解析 | 不怕你不会,就怕你没看

前言

这篇文章将会一起复习 css 选择器知识 , 理论与实践相结合 , 我们将会一起卷:

  • css 选择器&渲染引擎
  • 选择器分类与权重
  • 伪类和伪元素进阶
  • 属性选择器模式匹配
  • 优先级冲突

一、选择器底层实现原理

我们首先来看看选择器的底层实现 , 因为只有明白了上下游 , 在上下游中定位了选择器的位置 , 我们可以更好的理解{CSS 的选择器} , 所以一起探讨:

  • 渲染引擎的工作流程 ?
  • 渲染过程中 , 样式是如何匹配的 ?

渲染引擎工作流程

看下图 , 清晰的展现了渲染引擎的工作流程 , 接下来 , 对这张图逐帧学习🤡

1.1.1 解析阶段

1)HTML 解析

浏览器将 HTML 字符串解析为DOM树(Document Object Model),过程如下:

  • 词法分析 :将 HTML 文本分割为标签、属性等 Token(如<div>class="box")。
  • 语法分析 :将 Token 转换为节点对象(如ElementText),并构建树状结构。

特点 :解析是渐进式的,边下载边解析,无需等待整个文档加载完成。

2)CSS 解析

浏览器将 CSS 文本解析为CSSOM树(CSS Object Model),过程类似 DOM 解析:

  • 解析外部 CSS 文件(如<link>标签)或内联样式(如<style>标签)。
  • 合并用户代理默认样式(如浏览器内置的user agent styles)。

特点 :CSSOM 构建必须等待完整 CSS 文件加载完成,因为后续规则可能覆盖前面的样式。

1.1.2 构建 Render Tree

接下来构建渲染树(Render Tree)

在渲染树中 , 其实还有更详细的流程 , 我们展开如下图 :

  • 合并 DOM 和 CSSOM

    浏览器遍历 DOM 树,为每个可见节点(如display: none的节点会被忽略)匹配 CSS 规则,生成Render Tree

  • 样式匹配算法

    • 从右到左匹配 :现代浏览器(如 WebKit、Blink)采用 BFS 算法,优先匹配最右边的选择器(如.box p先匹配p,再向上查找.box),减少无效遍历。
    • 缓存机制:缓存常用选择器的匹配结果,避免重复计算。
  • 冲突解决

    根据 CSS 优先级(!important > ID > 类 > 标签)和层叠规则(后定义的样式覆盖先定义的)确定最终样式。

看到这里可以知道知道 CSS 选择器 , 他在渲染过程中 , 处于什么阶段了吗 ?,在构建 Render Tree 过程中 !样式匹配的时候!

接下来继续讲解这张图 , 也就是下游 ~

1.1.3 布局(Layout)
  • 计算几何信息
    确定每个节点在视口(Viewport)中的位置和尺寸,生成布局树(Layout Tree)。
  • 盒模型计算
    应用盒模型规则(如widthmarginfloat),处理文档流、定位和 Flexbox/Grid 布局。
  • 触发条件
    当 DOM 或样式变化时(如元素增删、窗口缩放),会触发重排(Reflow),导致布局重新计算。
1.1.4 绘制(Paint)
  • 将布局转换为像素
    遍历布局树,将每个节点的样式(如颜色、边框、阴影)绘制为像素,生成绘制记录(Paint Record)。
  • 分层优化
    复杂场景(如 3D 变换、透明度)会被分层处理,减少重复绘制。例如,position: fixed元素单独成层。
  • 触发条件
    样式变化(如背景色修改)会触发重绘(Repaint)。

绘制之后 ,进行合成 ~

1.1.5 合成(Composite)

主要有一下几个步骤

  • 生成最终图像
    将绘制的层按照顺序合并(Compositing),应用变换、透明度等效果,输出到屏幕。
  • GPU 加速
    现代浏览器使用 GPU 处理合成,提高复杂动画和滚动的流畅度。
  • 优化策略
    只更新变化的层(如滚动时仅重绘视口内区域),减少计算量。

通过上述渲染引擎的工作流程 ,了解 css 选择器在渲染引擎中的底层作用 , 现在我们继续讨论 css 的选择器 ~

二、选择器分类与权重

2.1 优先级计算表

我们直接用表格的形式 , 按照选择器的权重排序快速展现

选择器类型 权重值 示例 匹配范围 详细说明
内联样式(!important) 10000 <p style="color: red;"> 单个元素 直接写在 HTML 标签中的样式,带有 !important 声明时,具有最高优先级,会覆盖其他任何样式规则。不过,应谨慎使用 !important,因为它会破坏样式表的层叠规则,使代码难以维护。
ID 选择器 100 #main 唯一元素 通过元素的 id 属性来选择元素,由于 id 在 HTML 文档中必须是唯一的,所以该选择器能精准定位到单个元素。
类选择器 10 .card 多个元素 选择具有指定类名的所有元素。类名可以在多个元素上重复使用,因此适合用于批量设置样式。
属性选择器 10 [type="email"] 符合条件的元素 根据元素的属性及其值来选择元素。可以选择具有特定属性的元素,或者属性值满足特定条件的元素。
伪类选择器 10 :hover 动态状态元素 用于选择处于特定状态的元素,如鼠标悬停(:hover)、链接已访问(:visited)、元素获得焦点(:focus)等。这些状态是动态的,会根据用户的交互而改变。
元素选择器 1 p 所有同类元素 选择 HTML 文档中所有指定类型的元素,如所有的 <p> 标签、所有的 <div> 标签等。
通配符选择器 0 * 全部元素 选择 HTML 文档中的所有元素。通常用于全局设置样式,如重置默认边距和内边距。
伪元素选择器 1 ::before 元素前后内容 用于选择元素的特定部分,如元素的前面(::before)或后面(::after)插入的内容。伪元素实际上并不存在于 HTML 文档中,而是由 CSS 动态生成的。
否定伪类选择器 10 :not(p) 除指定元素外的其他元素 选择不匹配指定选择器的所有元素。例如,:not(p) 会选择除 <p> 标签之外的所有元素。
相邻兄弟选择器 组合权重 h1 + p 紧跟在指定元素后的相邻兄弟元素 选择紧跟在指定元素后面的具有相同父元素的相邻兄弟元素。例如,h1 + p 会选择紧跟在 <h1> 标签后面的 <p> 标签。
通用兄弟选择器 组合权重 h1 ~ p 指定元素后面的所有兄弟元素 选择指定元素后面的具有相同父元素的所有兄弟元素。例如,h1 ~ p 会选择 <h1> 标签后面的所有 <p> 标签。
子选择器 组合权重 ul > li 指定元素的直接子元素 选择指定元素的直接子元素。例如,ul > li 会选择 <ul> 标签的直接子元素 <li> 标签。
后代选择器 组合权重 div p 指定元素的所有后代元素 选择指定元素的所有后代元素,包括子元素、孙元素等。例如,div p 会选择 <div> 标签内的所有 <p> 标签。

我们现在知道了各个选择器的权重值,那么复合的选择器的权重怎么计算呢 ?

下面 , 我举一些例子来探讨一下 ~

2.1.1 复合选择器权重计算

复合选择器由多个简单选择器组合而成,其权重值是各个简单选择器权重值的总和。以下是更多复合选择器权重计算的示例:

css 复制代码
/* 权重计算示例 */
#header .nav > li.active a:hover {
  /* 100 (ID) + 10 (类) + 1 (元素) + 10 (类) + 1 (元素) + 10 (伪类) = 132 */
  color: #e74c3c;
}

body #content .article h2::first-letter {
  /* 1 (元素) + 100 (ID) + 10 (类) + 1 (元素) + 1 (伪元素) = 113 */
  font-size: 2em;
}

input[type="text"]:focus {
  /* 1 (元素) + 10 (属性选择器) + 10 (伪类) = 21 */
  border-color: blue;
}

section:not(.special) p {
  /* 1 (元素) + 10 (否定伪类) + 1 (元素) = 12 */
  color: green;
}

在实际应用中,当多个样式规则应用到同一个元素时,浏览器会根据选择器的权重来决定使用哪个样式。权重值越高的选择器,其样式规则优先级越高。如果多个选择器的权重值相同,则后定义的样式规则会覆盖先定义的样式规则。

我们举个例子

你正在开发一个博客网站,页面结构包含头部导航栏、文章列表以及页脚。在设计样式时,你需要针对不同部分的元素应用不同的样式,同时要处理好复合选择器的权重问题,以确保样式能够按照预期生效。

HTML 结构

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="styles.css">
    <title>博客网站</title>
</head>
<body>
    <!-- 头部导航栏 -->
    <header id="main-header">
        <nav class="nav-menu">
            <ul>
                <li><a href="#">首页</a></li>
                <li><a href="#">文章</a></li>
                <li><a href="#">关于</a></li>
            </ul>
        </nav>
    </header>
    <!-- 文章列表 -->
    <main id="article-list">
        <article class="article-item">
            <h2>文章标题 1</h2>
            <p>文章内容 1...</p>
            <a href="#" class="read-more">阅读更多</a>
        </article>
        <article class="article-item">
            <h2>文章标题 2</h2>
            <p>文章内容 2...</p>
            <a href="#" class="read-more">阅读更多</a>
        </article>
    </main>
    <!-- 页脚 -->
    <footer id="main-footer">
        <p>版权所有 &copy; 2025 博客网站</p>
    </footer>
</body>
</html>

CSS 样式及权重计算分析

css 复制代码
/* 导航栏链接样式 */
#main-header .nav-menu ul li a {
    /* 权重计算:
       #main-header 是 ID 选择器,权重为 100
       .nav-menu 是类选择器,权重为 10
       ul 是元素选择器,权重为 1
       li 是元素选择器,权重为 1
       a 是元素选择器,权重为 1
       总权重 = 100 + 10 + 1 + 1 + 1 = 113
    */
    color: #333;
    text-decoration: none;
}

/* 文章标题样式 */
#article-list .article-item h2 {
    /* 权重计算:
       #article-list 是 ID 选择器,权重为 100
       .article-item 是类选择器,权重为 10
       h2 是元素选择器,权重为 1
       总权重 = 100 + 10 + 1 = 111
    */
    color: #e74c3c;
}

/* 文章阅读更多链接样式 */
#article-list .article-item a.read-more {
    /* 权重计算:
       #article-list 是 ID 选择器,权重为 100
       .article-item 是类选择器,权重为 10
       a 是元素选择器,权重为 1
       .read-more 是类选择器,权重为 10
       总权重 = 100 + 10 + 1 + 10 = 121
    */
    color: #2980b9;
    text-decoration: underline;
}

/* 页脚段落样式 */
#main-footer p {
    /* 权重计算:
       #main-footer 是 ID 选择器,权重为 100
       p 是元素选择器,权重为 1
       总权重 = 100 + 1 = 101
    */
    color: #7f8c8d;
    text-align: center;
}

分析说明

  • 导航栏链接样式:借助复合选择器精准定位到导航栏里的链接元素,总权重为 113。
  • 文章标题样式:能够准确选中文章列表中的标题元素,总权重为 111。
  • 文章阅读更多链接样式:专门针对文章中的"阅读更多"链接,由于其权重为 121,高于其他链接选择器,所以能保证该样式正确应用。
  • 页脚段落样式:可选中页脚的段落元素,总权重为 101。

通过对复合选择器权重的合理计算和运用,你可以确保每个元素都能应用到预期的样式,避免样式冲突的问题。

三、伪类和伪元素进阶

3.1 结构伪类进阶

3.1.1 奇偶行交替
css 复制代码
   /* 选择 tbody 内的偶数行 */
  tbody tr:nth-child(even) {
      background-color: #b31635;
  }

  /* 选择 tbody 内的奇数行 */
  tbody tr:nth-child(odd) {
      background-color: #199523;
  }

在浏览器中打开该 HTML 文件,表格的表头不会受奇偶行变色样式的影响,而表格主体部分的奇数行背景颜色会显示为 #199523(绿色),偶数行背景颜色会显示为 #b31635(红色)。

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>奇偶行交替表格</title>
    <style>
      // 放这里
    </style>
</head>

<body>
    <table>
        <thead>
            <tr>
                <th>姓名</th>
                <th>年龄</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>张三</td>
                <td>25</td>
            </tr>
            <tr>
                <td>李四</td>
                <td>30</td>
            </tr>
            <tr>
                <td>王五</td>
                <td>22</td>
            </tr>
        </tbody>
    </table>
</body>

</html>
    
3.1.2 分页控制
css 复制代码
/* 显示第2页内容 */
.page-content:target {
  display: block;
}

.page-content {
  display: none;
}

当你点击导航链接时,URL 会发生变化,例如点击 "页面 2" 链接,URL 会变为 yourpage.html#page2,此时页面 2 的内容就会显示出来,其他页面内容则保持隐藏。

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>分页内容显示</title>
    <style>
      // 放这里
    </style>
</head>

<body>
    <!-- 导航链接 -->
    <ul>
        <li><a href="#page1">页面 1</a></li>
        <li><a href="#page2">页面 2</a></li>
        <li><a href="#page3">页面 3</a></li>
    </ul>
    <!-- 页面内容 -->
    <div id="page1" class="page-content">
        <h2>页面 1</h2>
        <p>这是页面 1 的内容。这里可以展示各种与页面 1 相关的信息,比如产品介绍、新闻资讯等。</p>
    </div>
    <div id="page2" class="page-content">
        <h2>页面 2</h2>
        <p>这是页面 2 的内容。页面 2 可能会有不同的主题,例如技术文章、案例分析等。</p>
    </div>
    <div id="page3" class="page-content">
        <h2>页面 3</h2>
        <p>这是页面 3 的内容。页面 3 或许会呈现一些娱乐内容、用户评价等。</p>
    </div>
</body>

</html>
    

3.2 伪元素高级用法

3.2.1 动态内容生成
css 复制代码
/* 自动添加版权信息 */
body::after {
  content: "Copyright " attr(data-year);
  display: block;
  text-align: center;
  padding: 20px;
  background-color: #f8f9fa;
}

这里我添加了动画 , 为了突出自动生成 , 上图是我刷新页面后的结果

xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自动添加版权信息</title>
    <style>
        body::after {
            content: "自动添加 - Copyright " attr(data-year);
            display: block;
            text-align: center;
            padding: 20px;
            background-color: #222;
            color: white;
            font-size: 18px;
            border-top: 2px solid #ccc;
            position: fixed;
            bottom: -100px;
            left: 0;
            right: 0;
            opacity: 0;
            animation: slideUp 1s ease-out 0.5s forwards;
        }

        @keyframes slideUp {
            to {
                bottom: 0;
                opacity: 1;
            }
        }
    </style>
</head>

<body data-year="2025">
    <h1>欢迎访问我的网页</h1>
    <p>这是网页的主要内容,你可以在这里展示各种信息。</p>
</body>

</html>    

我们分析一下

content 属性

  • content 属性是 body::after 中最为关键的属性,它用于定义要插入的内容。在上述示例里,content: "Copyright " attr(data-year); 意味着会插入一段文本 "Copyright ",接着通过 attr(data-year) 函数获取 body 元素的 data-year 属性值。所以,最终插入的内容是 "Copyright 2025"
  • attr() 函数能够获取 HTML 元素的属性值,这让插入的内容更具动态性。

display 属性

  • display: block; 把插入的内容作为块级元素来显示,这表明该内容会独占一行,并且可以设置宽度、高度、内边距等属性。

text-align 属性

  • text-align: center; 让插入的内容在水平方向上居中显示。

padding 属性

  • padding: 20px; 为插入的内容添加了 20 像素的内边距,使内容与周围元素之间有一定的间隔。

background-color 属性

  • background-color: #f8f9fa; 为插入的内容设置了背景颜色,方便与页面的其他部分区分开来。

自动添加的过程

  • 当浏览器渲染页面时,会依据 CSS 规则对页面元素进行样式设置。在处理 body 元素时,会检测到 body::after 伪元素的规则。
  • 接着,浏览器会根据 content 属性的定义,从 body 元素的 data-year 属性中获取值,然后将组合好的内容插入到 body 元素的末尾。
  • 最后,按照其他 CSS 属性(如 displaytext-alignpaddingbackground-color 等)对插入的内容进行样式设置,从而实现版权信息的自动添加。
3.2.2 文本截断

我写过一篇文章 , 可以看看 :

面试官😏 :文本太长,超出部分用省略号 ,怎么搞?我:🤡

css 复制代码
/* 多行文本截断 */
.text-truncate {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

四、属性选择器模式匹配

4.1 表单验证

借助属性选择器和 :valid:invalid 伪类对密码输入框进行验证,依据输入内容是否符合规则展示不同样式。

css 复制代码
/* 强密码验证 */
input[type="password"]:valid {
  border-color: #22c55e;
  box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.1);
}

input[type="password"]:invalid {
  border-color: #ef4444;
  box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
  • input[type="password"]:valid:当密码输入框内的内容符合验证规则(这里要求必填且长度至少为 6 个字符)时,输入框的边框颜色变为绿色(#22c55e),同时添加绿色的阴影效果。
  • input[type="password"]:invalid:当密码输入框内的内容不符合验证规则时,输入框的边框颜色变为红色(#ef4444),并添加红色的阴影效果。
xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>表单密码验证</title>
    <style>
        input[type="password"]:valid {
            border-color: #22c55e;
            box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.1);
        }

        input[type="password"]:invalid {
            border-color: #ef4444;
            box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
        }
    </style>
</head>

<body>
    <form>
        <label for="password">请输入密码:</label>
        <input type="password" id="password" required minlength="6">
        <input type="submit" value="提交">
    </form>
</body>

</html>    

4.2 图标字体

通过属性选择器和伪元素 ::before 依据元素的 icon 属性动态显示不同的图标。

css 复制代码
/* 动态图标映射 */
[icon="home"]::before {
  font-family: 'Material Icons';
  content: "🏠";
}

[icon="search"]::before {
  content: "🔍";
}
  • [icon="home"]::before:当元素的 icon 属性值为 "home" 时,在元素内容(主页)之前插入一个房屋图标(🏠)。
  • [icon="search"]::before:当元素的 icon 属性值为 "search" 时,在元素内容(搜索)之前插入一个搜索图标(🔍)。
xml 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图标字体动态映射</title>
    <style>
        [icon="home"]::before {
            font-family: 'Material Icons';
            content: "🏠";
        }

        [icon="search"]::before {
            content: "🔍";
        }
    </style>
</head>

<body>
    <span icon="home">主页</span>
    <span icon="search">搜索</span>
</body>

</html>    

五、优先级冲突解决方案

5.1 权重提升策略

css 复制代码
/* 最高优先级 */
.urgent {
  color: #e74c3c !important;
}

/* 组合选择器 */
div > ul > li.active {
  font-weight: bold;
}
  • .urgent 类使用了 !important 声明,这会让它的 color 属性具有最高优先级,即使有其他规则试图覆盖它也不行。
  • div > ul > li.active 是一个组合选择器,它通过指定元素的层级关系,让规则更加具体,因此具有较高的权重。只有同时满足在 div 下的 ul 中的 li 元素且具有 active 类的元素才会应用该样式。

5.2 权重可视化工具

javascript 复制代码
// 自制权重计算器
function calculateSpecificity(selector) {
  const pattern = /([#.])(\w+)|(\w+)/g;
  let [id, cls, tag] = [0, 0, 0];
  
  selector.replace(pattern, (match, hash, classname, element) => {
    if (hash === '#') id++;
    if (hash === '.') cls++;
    if (element) tag++;
  });
  
  return {
    specificity: `${id},${cls},${tag}`,
    weight: id * 100 + cls * 10 + tag
  };
}

// 使用示例
console.log(calculateSpecificity('#header .nav > li.active'));
// 输出: { specificity: "1,2,1", weight: 121 }
  • calculateSpecificity 函数接受一个 CSS 选择器作为参数。
  • 使用正则表达式 /([#.])(\w+)|(\w+)/g 来匹配选择器中的 id、类和标签。
  • 通过 replace 方法遍历匹配结果,根据不同的匹配情况增加 idclstag 的计数。
  • 最后返回一个对象,包含选择器的特异性(格式为 id,cls,tag)和计算得到的权重值。权重值的计算规则是 id * 100 + cls * 10 + tag,这是因为 id 的权重最高,类其次,标签最低。

总结

点个赞吧 ~

相关推荐
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端
爱敲代码的小鱼10 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax