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 的权重最高,类其次,标签最低。

总结

点个赞吧 ~

相关推荐
uhakadotcom1 分钟前
Syslog投递日志到SIEM:基础知识与实践
后端·面试·github
uhakadotcom5 分钟前
Flume 和 Logstash:日志收集工具的对比
后端·面试·github
uhakadotcom7 分钟前
Kibana入门:数据分析和可视化的强大工具
后端·面试·github
代码or搬砖11 分钟前
ECharts实现数据可视化
前端·信息可视化·echarts
ssshooter16 分钟前
问 AI 一个 contenteditable 的问题半天没答案
前端·javascript·程序员
Georgewu29 分钟前
【HarmonyOS Next】鸿蒙应用弹框和提示气泡详解(二)之浮层(OverlayManager),半模态页面(bindSheet),全模态页面(bindC
前端·华为·harmonyos
池鱼ipou30 分钟前
《JavaScript的“套娃陷阱”:90%程序员栽过的三种复制大坑》
前端·javascript·面试
伶俜monster32 分钟前
Threejs 物理引擎高阶:作用力、休眠与组合体奥秘
前端·three.js
用户261245834016136 分钟前
部署项目,console.log为什么要去掉?
前端
用户6133467165341 分钟前
体育赛事即时比分 分析页面的开发技术架构与实现细节
前端·后端