CSS中的after和before选择器实际上属于伪元素,完整写法是::after和::before。它们的作用是在某个元素的内部最前面或最后面插入一个虚拟的子元素,这个子元素不会出现在HTML源码里,但会参与渲染。很多开发者第一次接触时容易把它们理解成加在元素外部的前后,其实它们始终位于元素盒子的内部,相当于第一个或最后一个子元素。由于伪元素不占用真实DOM节点,修改样式时不用改动HTML结构,这对维护组件样式尤其有用。下面先给出一张示意图,帮助建立直观印象。

要使用这两个伪元素,最关键的一点是必须设置content属性。即使你只想画一个纯装饰的图形,也需要写content: "";,否则伪元素不会生成。content的值可以是字符串、计数器、属性值引用等。比如想在每个链接前面加个箭头图标,可以这样写:
a::before {
content: "→";
margin-right: 4px;
color: #666;
}
上面代码会在每个<a>元素内部最前面插入一个箭头字符。如果content换成空字符串,则常用来配合width、height、background等属性创建纯装饰块,比如分割线、小圆点或气泡尾巴。伪元素默认是行内元素,但你可以通过display属性改成块级或inline-block,从而设置宽高。
注意,伪元素继承原始元素的字体、颜色等样式,但一些属性比如list-style、border-collapse不会作用于伪元素。另外,表单替换元素(如<input>、<img>)以及部分自闭合标签(如<br>、<hr>)不支持伪元素,因为其内容模型不允许生成子元素。
定位和层叠关系
伪元素经常和position配合使用。如果你把伪元素设置为position: absolute;,它就会相对于最近的已定位祖先元素进行定位。但一定要记得给原始元素设置position: relative;,否则伪元素可能根据视口定位,跑得满屏都是。一个经典应用是给提示框添加小三角:
.tooltip {
position: relative;
display: inline-block;
background: #333;
color: #fff;
padding: 6px 12px;
border-radius: 4px;
}
.tooltip::after {
content: "";
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
border-width: 8px 8px 0;
border-style: solid;
border-color: #333 transparent transparent;
}
这段代码在.tooltip元素底部中心生成了一个指向下方的小三角。因为伪元素使用了绝对定位,所以需要给.tooltip设置相对定位作为包含块。利用border画出三角形是常见技巧,这里不做展开。伪元素还可以和z-index一起控制层级,比如让它浮在文字上方作为装饰,或者沉在底部作为背景。
另一个值得注意的细节是,伪元素在层叠上下文中与普通子元素地位相同。如果原始元素创建了层叠上下文(比如设置了opacity小于1、transform等),伪元素的层级会受这个上下文约束。在排查伪元素被遮挡时,不妨看看祖先元素是否创建了新的层叠上下文。
经典使用场景
第一个高频场景是清除浮动。过去常用after伪元素配合clear: both来闭合浮动,避免父元素高度塌陷。代码如下:
.clearfix::after {
content: "";
display: block;
clear: both;
}
这个方法比在HTML末尾手动添加空<div>更简洁。它是在父元素内部最后面生成一个块级伪元素,强制撑开父容器。当然现在有flex或grid布局后,清浮动的需求减少了,但了解这个技巧有助于理解伪元素的生成时机。
第二个场景是装饰性内容。比如给外部链接添加一个小图标,或者给列表项自定义序号。你不必在HTML里写额外标签,所有视觉装饰都交给CSS管理。下面示例给每个列表项前面添加一个自定义的小方块标记:
li::before {
content: "";
display: inline-block;
width: 8px;
height: 8px;
background: #e67e22;
margin-right: 8px;
border-radius: 2px;
}
这种用法让内容与表现彻底分离,后续修改样式时不用动HTML。但要注意,伪元素生成的内容不支持文本选择(部分浏览器可以选中但复制不完整),也不利于屏幕阅读器朗读,因此重要的文字内容不要放在content里,只放纯粹的装饰性符号或空字符串。
常见问题与排查思路
很多开发者遇到伪元素不显示的情况,首先检查content属性是否遗漏。因为content: "";很容易被当成没用的空值而跳过。第二个常见原因是display没设置成块级或inline-block,导致宽高无效。第三个原因是原始元素是替换元素或不支持伪元素的标签。此外,如果原始元素设置了overflow: hidden;,伪元素可能会被裁剪掉,特别是绝对定位超出边界时。
另一个容易混淆的概念是before和after的插入位置。它们不是加在元素的前面或后面,而是加在元素内容的开头或结尾。比如一个<p>元素,::before出现在段落文字之前,::after出现在段落文字之后,但都在<p>盒子的内部。可以通过给::before设置背景色,观察它是否被算在元素宽度内来验证。
另外,伪元素也可以使用transition和animation。例如,鼠标悬停时让::after渐变出现,代码大致如下:
.button::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 2px;
background: #3498db;
transform: scaleX(0);
transition: transform 0.3s ease;
}
.button:hover::after {
transform: scaleX(1);
}
不过需要注意的是,部分旧浏览器对伪元素动画支持不佳,而且伪元素不能通过@keyframes直接选择为独立目标(但可以写在原始元素上,然后在关键帧里改变伪元素相关属性)。实际项目中,建议先在主流浏览器上测试动画效果。
最后提一个容易踩坑的地方:在content里写字符串时,换行和特殊字符需要转义。比如内容里包含引号,要用反斜杠转义,或者改用单引号与双引号搭配。如果希望给伪元素添加鼠标事件(比如点击),是做不到的,因为伪元素不是真实DOM节点。这类交互需求还是需要用真实元素来实现。