先记住这个答案
伪类选的是已经存在的元素在某种状态下的样子,比如 :hover 是鼠标悬停时,:first-child 是身为第一个子元素时。伪元素创建的是 DOM 里不存在的虚拟内容,比如 ::before 插入生成内容,::first-line 选中渲染后的第一行文字。简单说,伪类像是给元素贴了一个「当前状态」的标签,伪元素像是凭空造了一个子节点。
- 伪类选状态,伪元素选虚拟部分
- 伪类用
:,伪元素用:: - 伪类可连写,部分伪元素允许嵌套
- 旧浏览器的
:before也能用但不推荐
冒号的写法规则
CSS2 时代不区分伪类和伪元素,全用单冒号。CSS3 为了明确区分,把伪元素改成了双冒号。
/* 伪类:始终用单冒号 */
a:hover { color: red; }
li:first-child { font-weight: bold; }
input:focus { outline: 2px solid blue; }
/* 伪元素:推荐用双冒号 */
p::before { content: "▸ "; }
p::first-line { font-weight: bold; }
p::selection { background: yellow; }
/* 旧写法也能用,浏览器兼容 */
p:before { content: "▸ "; }新代码一律用双冒号写伪元素。唯一的例外是如果要兼容 IE8——IE8 不认 ::before 但认 :before。不过现在已经不太需要考虑 IE8 了。
伪类可以连写,伪元素不能
伪类可以叠加,比如 a:hover:focus 同时要求鼠标悬停和键盘聚焦。伪元素不能任意连写,p::before::after 是无效的;但有明确的嵌套例外:若 ::before 设为 display: list-item,可用 ::before::marker 选中它生成的列表标记。
伪类和伪元素可以一起用,但伪元素必须写在最后。p:first-child::first-line 合法,p::first-line:first-child 不合法。
/* 第一个段落的第一行加粗(合法) */
p:first-child::first-line {
font-weight: bold;
}
/* 鼠标悬停时改变 before 的内容(合法) */
.icon:hover::before {
color: blue;
}这种组合在实际项目里很常见。比如鼠标悬停在菜单项上时,让 ::before 里的图标变色。
常见的伪类和伪元素分类
伪类可以分成几大类。动态伪类有 :hover 和 :focus 等。结构伪类有 :first-child 和 :nth-child 等。表单伪类有 :required 和 :valid 等。还有否定和关系伪类如 :not() 和 :has()。
伪元素数量少得多,常用的有 ::before 和 ::after(生成内容)、::first-line 和 ::first-letter(选中文本的一部分)、::selection(选中高亮)、::placeholder(占位文字)、::marker(列表符号)。
容易答错的地方
- 说不清两者的本质区别
- 「单冒号是伪类,双冒号是伪元素」只描述了写法,没说清本质。关键区别在于:伪类不创建新东西,只是选中已有元素的某种状态。伪元素会创建一个 DOM 中不存在的虚拟节点或选中元素的一部分文本。
- 以为伪元素能任意连写
p::before::first-line无效,不能把任何伪元素都接在另一个后面。::before::marker是有条件的例外,前提是::before生成列表项。写组合选择器时要查具体伪元素支持的嵌套关系。
面试官还会怎么问?
before 和 after 创建的内容能用 JavaScript 访问吗?
不能直接访问。document.querySelector('::before') 返回 null,不能给伪元素加事件监听。但可以用 getComputedStyle(element, '::before').content 读取它的样式值。
placeholder 是伪类还是伪元素?
::placeholder 是伪元素,用双冒号,它选中的是输入框里占位文字的虚拟部分。:placeholder-shown 是伪类,用单冒号,它选中的是「正在显示占位文字」这个状态下的输入框元素本身。两者一个改占位文字的样式,一个根据有没有占位文字来选中整个输入框。
marker 伪元素是干什么的?
::marker 选中列表项前面的符号(圆点、数字等)。比如 li::marker { color: red; } 可以只让列表前面的圆点变红,不影响列表内容的颜色。以前要改列表符号的颜色只能用 ::before 模拟,现在用 ::marker 更直接。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。