深入理解 CSS 伪类和伪元素的本质区别

在写前端CSS样式的时候,经常用到这种:hover或者::before有冒号的写法。我很疑惑,为什么有些是单冒号,有些又是双冒号呢?

后来我才知道这种写法也区分为伪类伪元素

伪类和伪元素是什么?

伪类(Pseudo-classes):​用于选择处于特定状态的元素。也可以理解为,当元素处于某种状态的时候,给它加上一个"类"来定义样式。

  • 语法:单个冒号:,例如:hover

伪元素(Pseudo-elements):用于选择元素的特定部分。可以理解为,它在文档中创建了一个虚拟的"元素"来设置样式。

  • 语法:双冒号::,例如::before

只要是单个冒号的就一定是伪元素吗?

在现代CSS3点规范中,所有使用双冒号::语法的选择器都被定义为伪元素。这是W3C为了明确区分伪类和伪元素而引入的约定。

但在早期的CSS中,伪元素也使用单冒号,因为当时没有区分语法。所以为了向后兼容,大多数的浏览器还是会支持:before:after等单冒号的写法。

但新的伪元素只支持双冒号,比如:::selection


伪类(Pseudo-classes)

伪类用于选择处于特定状态的元素,比如用户交互状态、结构位置等。

常见伪类示例:

  • :hover:鼠标悬停时
  • :focus:元素获得焦点时(如输入框)
  • :active:元素被激活时(如点击按钮)
  • :visited:链接已被访问过
  • :first-child / :last-child:第一个/最后一个子元素
  • :nth-child(n):第 n 个子元素
  • :not(selector):排除匹配 selector 的元素

示例:

css 复制代码
a:hover {
  color: red;
}

input:focus {
  border: 2px solid blue;
}

li:first-child {
  font-weight: bold;
}

伪类使用单冒号


伪元素(Pseudo-elements)

伪元素用于创建并样式化文档中不存在的虚拟元素,比如段落首字母、选中文本、元素前后插入内容等。

常见伪元素:

  • ::before:在元素内容前插入内容
  • ::after:在元素内容后插入内容
  • ::first-letter:段落的第一个字母
  • ::first-line:段落的第一行
  • ::selection:用户选中的文本(部分浏览器需加前缀)

示例:

css 复制代码
p::first-letter {
  font-size: 2em;
  color: gold;
}

.quote::before {
  content: """;
}

.quote::after {
  content: """;
}

::selection {
  background: yellow;
  color: black;
}

伪元素使用双冒号::before),这是CSS3的规范写法。出于兼容性考虑,旧代码可能仍用单冒号(如:before),现代项目建议用双冒号。


主要区别

特性 伪类(Pseudo-class) 伪元素(Pseudo-element)
作用对象 已存在的元素的状态或位置 创建不存在的虚拟内容或部分
语法 单冒号(如 :hover 双冒号(如 ::before
是否生成内容 是(常配合 content 属性)
示例 a:hover, :nth-child(2) ::first-letter, ::after

示例效果

一个简单的待办事项列表

完整代码

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>伪类vs伪元素示例</title>
    <style>
        .container {
            max-width: 500px;
            margin: 0 auto;
            background: white;
            padding: 30px;
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        }

        h1 {
            text-align: center;
            color: #2c3e50;
            margin-bottom: 30px;
        }

        .explanation {
            background: #f8f9fa;
            padding: 15px;
            border-radius: 8px;
            margin-bottom: 20px;
            border-left: 4px solid #3498db;
        }

        .todo-list {
            list-style: none;
            padding: 0;
        }

        .todo-list li {
            padding: 15px;
            margin: 8px 0;
            background: white;
            border: 2px solid #e9ecef;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.3s ease;
            position: relative;
        }

        /* === 伪类样式 === */
        /* 第一个子元素 */
        .todo-list li:first-child {
            border-left: 4px solid #e74c3c;
        }

        /* 偶数项 */
        .todo-list li:nth-child(even) {
            background-color: #f8f9fa;
        }

        /* 悬停效果 */
        .todo-list li:hover {
            background-color: #aab49b;
            color: white;
            transform: translateX(10px);
            border-color: #aab49b;
        }

        /* 点击效果 */
        .todo-list li:active {
            background-color: #2ecc71;
            transform: scale(0.98);
        }

        /* === 伪元素样式 === */
        /* 前面的图标 */
        .todo-list li::before {
            content: "📌";
            margin-right: 10px;
            transition: all 0.3s ease;
        }

        /* 悬停时图标变化 */
        .todo-list li:hover::before {
            content: "🔥";
            transform: scale(1.2);
        }

        /* 后面的装饰线 */
        .todo-list li::after {
            content: "";
            position: absolute;
            left: 0;
            bottom: 0;
            width: 0;
            height: 3px;
            background: linear-gradient(90deg, #e74c3c, #669521);
            transition: width 0.3s ease;
        }

        .todo-list li:hover::after {
            width: 100%;
        }

        /* 首字母样式 */
        .todo-list li::first-letter {
            font-size: 1.3em;
            color: #2c3e50;
            font-weight: bold;
        }

        .todo-list li:hover::first-letter {
            color: white;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>伪类 vs 伪元素 演示</h1>
        
        <div class="explanation">
            <p><strong>伪类(Pseudo-class)</strong>:选择元素的特定<strong>状态</strong></p>
            <p><strong>伪元素(Pseudo-element)</strong>:选择元素的特定<strong>部分</strong></p>
        </div>

        <ul class="todo-list">
            <li>学习 CSS 伪类</li>
            <li>理解伪元素</li>
            <li>完成项目练习</li>
            <li>复习知识点</li>
        </ul>
    </div>

    <script>
        // 添加点击切换完成状态的功能
        document.querySelectorAll('.todo-list li').forEach(item => {
            item.addEventListener('click', function() {
                this.classList.toggle('completed');
            });
        });
    </script>
</body>
</html>

在这个示例中,可以清晰地看到:

伪类(操作的是整个元素)

  • :first-child:操作第一个<li>元素的整体样式
  • :nth-child(even):操作偶数位置<li>的整体背景
  • :hover:操作鼠标悬停时<li>的整体状态变化

伪元素(操作的是元素的一部分)

  • ::before:在<li>内容之前插入新内容(图标)
  • ::after:在<li>内容之后插入装饰线条
  • ::first-letter:只样式化<li>文本的第一个字母

简单记忆:伪类是状态选择器,伪元素是内容生成器。


总结

伪类

  • 选择元素的特定状态(如:hover、:focus)
  • 语法使用单冒号 (如:hover
  • 不生成新内容,只针对元素本身的状态变化
  • 常见用途:用户交互反馈、结构位置选择

伪元素

  • 创建并样式化元素的特定部分虚拟内容
  • 语法使用双冒号 (如::before
  • 常配合content属性生成新内容
  • 常见用途:插入装饰元素、样式化文本部分

通过上面的内容,我也终于搞懂了伪类和伪元素的区别。

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《async/await 到底要不要加 try-catch?异步错误处理最佳实践》

《Vue3 和 Vue2 的核心区别?很多开发者都没完全搞懂的 10 个细节》

《Java 开发必看:什么时候用 for,什么时候用 Stream?》

《这 10 个 MySQL 高级用法,让你的代码又快又好看》

相关推荐
ccnocare几秒前
浅浅看一下设计模式
前端
Lee川4 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix31 分钟前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人34 分钟前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl38 分钟前
OpenClaw 深度技术解析
前端
崔庆才丨静觅41 分钟前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空1 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust
Mr Xu_1 小时前
Vue 3 中计算属性的最佳实践:提升可读性、可维护性与性能
前端·javascript