在写前端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 个细节》