我收到你的大纲了!这题目太真实了,谁刚学前端没被这俩货坑过啊。行,我这就开整,保证给
好,我收到你的大纲了!这题目太真实了,谁刚学前端没被这俩货坑过啊。行,我这就开整,保证给你整出一篇像刚喝完奶茶在办公室吐槽写出来的东西,代码管够,废话也管够(但不是那种AI废话)。

我收到你的大纲了!这题目太真实了,谁刚学前端没被这俩货坑过啊。行,我这就开整,保证给
好,我收到你的大纲了!这题目太真实了,谁刚学前端没被这俩货坑过啊。行,我这就开整,保证给你整出一篇像刚喝完奶茶在办公室吐槽写出来的东西,代码管够,废话也管够(但不是那种AI废话)。
伪类和伪元素傻傻分不清?前端新人避坑指南(附实战用法)
刚入门前端那会儿,我真以为 ::before 和 :hover 是一个妈生的
说实话,我现在看到当年自己写的代码都想穿越回去抽自己。记得第一次面试的时候,面试官问我:“你说说伪类和伪元素有啥区别?” 我当时那个自信啊,张口就来:“这不都是一个冒号两个冒号的区别嘛,功能都差不多,都是选不到的东西用来选。”
面试官那个表情我至今难忘,就是那种"这孩子废了"的微笑。
后来进了公司,我亲眼看到组里一个大佬用 ::before 做了个超酷的按钮 hover 效果,我兴冲冲地抄过来,把双冒号改成单冒号 :before,心想这不都一样嘛还少打个冒号呢。结果?毛反应都没有。我当时还在那调试了半小时,怀疑人生到以为浏览器坏了。最后大佬路过,瞥了一眼我的屏幕:“你这不是伪元素吗?单冒号那是 IE8 的古董写法,而且你 content 呢?没 content 你写一百个 before 也白搭啊兄弟。”
那一刻我才意识到,这俩玩意儿根本不是一家人,连远房亲戚都算不上。
HTML里那些看不见摸不着却能改天换地的小妖精
前端这个活儿最魔幻的地方就在这儿——你写的 HTML 明明就那么多标签,但到了页面上,总感觉有些"东西"存在,可你 F12 打开开发者工具翻个底朝天也找不到它们的具体标签。就像你感觉房间里有人,但开灯一看啥也没有,这种灵异事件在 CSS 界天天上演。
比如你给按钮写了个 :hover,鼠标一放上去颜色变了,可 HTML 里哪有这个"hover 状态"的标签啊?再比如你写了个 ::before,在里面塞了个小图标,DOM 树里你也找不到这个图标的 div 或者 span,但它确实就在那里,还占位置,还能点(如果你设置的话)。
这些小妖精就是我们要聊的主角。搞清楚它们的底细,你写 CSS 的时候就能从"蒙眼射箭"进化到"指哪打哪",而且从调试到性能优化都能玩转。别觉得这是基础中的基础就不当回事,我见过三年经验的前端还在用 JS 实现那些伪元素三行代码就能搞定的事儿,组长看了直摇头。
别再把 :first-child 和 ::first-letter 混着用了,面试官都笑出声了
来,先做个小测试,看你现在能不能分清。下面这几个选择器,哪些是伪类,哪些是伪元素:
:first-child
::first-letter
:nth-of-type(2)
::placeholder
:active
::selection
如果你脑子里还需要反应一下,那这篇文章你看对了。其实有个特别简单的判断方法,我后面会详细说,但现在先记住:伪类是关于状态的,伪元素是关于内容的。
:first-child 是在说"这个元素是家里老大"这件事,是一种状态;而 ::first-letter 是在说"我要把这个段落的第一个字母单独拎出来打扮一下",是在操作内容。一个是"你是谁",一个是"我要造个新东西"。
面试的时候如果连这都要想半天,真的,面试官内心已经在给你打分了——不是什么高分。因为这意味着你写 CSS 的时候思路是乱的,不知道自己在操作什么,出了 bug 也只能靠试。
从"我以为"到"原来如此":伪类 vs 伪元素血泪辨析
好了,不吓唬你了,咱们正经(相对正经)开始掰扯这两兄弟。
伪类其实是给元素加状态标签
你想想,CSS 选择器最基本的是啥?是标签名、class、id,对吧?这些都是实实在在的, HTML 里写了个 div class="box",那你写 .box 就能选中它。但问题是,有些事情是动态发生的,比如你鼠标移上去了,或者这个 input 被禁用了,或者这是列表里的第一个li——这些"身份"或者"状态"是临时性的,你不可能在 HTML 里给每个状态都写个 class 吧?
<!-- 你不会想这么干的 -->
<div class="box hover">鼠标在这呢</div>
<div class="box normal">鼠标走了</div>
这得多累啊,而且鼠标一移走你还得用 JS 去改 class。伪类就是来解决这个的。它就像是给元素贴了个临时标签,告诉浏览器:“我要选中那个正在被鼠标hover的家伙”、“我要那个被禁用掉的 input”。
:hover、:focus 这些不是样式,是元素的"情绪开关"
咱们用最最常见的 :hover 开刀。很多人以为 :hover 是个样式,其实不是,它是选择器的一部分,是用来选中元素的。只不过选中的时机很特殊——只有当鼠标指针悬停在元素上的时候,这个选择器才生效。
/* 这个选择器的意思是:选中所有 class 为 btn 且正在被鼠标悬停的 button 元素 */
button.btn:hover {
background-color: #ff6b6b;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3);
transition: all 0.3s ease;
}
/* :focus 同理,选中的是当前获得键盘焦点的 input */
input.form-control:focus {
border-color: #4ecdc4;
outline: none; /* 去掉那个丑丑的默认蓝框 */
box-shadow: 0 0 0 3px rgba(78, 205, 196, 0.2);
}
你看到了吗?:hover 和 :focus 前面都有个冒号,后面跟着的是常规的 CSS 属性。它们就是个筛选条件,筛的是"此刻处于某种状态的元素"。
:nth-child(2n) 这种选择器背后藏着数学鬼才的执念
这个选择器我当年看了半天文档才搞懂,其实特别实用,特别是做表格或者列表的时候。nth-child 里面的参数可以是个数字,可以是关键词(odd/even),也可以是公式。
/* 基础用法:选中第3个li */
li:nth-child(3) {
background: gold;
}
/* 关键词:奇数行和偶数行,做斑马纹表格必备 */
tr:nth-child(odd) {
background-color: #f8f9fa;
}
tr:nth-child(even) {
background-color: #ffffff;
}
/* _formula 模式:这才是精髓 */
/* 2n 表示 2×n,n 从0开始计数,所以选中的是第0、2、4、6...个,也就是偶数 */
li:nth-child(2n) {
margin-right: 0; /* 每行两个 item 时,去除偶数项的右边距 */
}
/* 3n+1 表示第1、4、7、10...个,也就是每3个中的第一个 */
.grid-item:nth-child(3n+1) {
clear: both; /* 清除浮动,开始新的一行 */
}
/* 负数也能用,选中前5个 */
li:nth-child(-n+5) {
font-weight: bold;
}
/* 从第6个开始往后全选 */
li:nth-child(n+6) {
opacity: 0.6;
}
这里有个坑我得提醒你:nth-child 数的是所有兄弟元素,不限于同类型。比如你有 ul 里面混杂着 li 和 div,li:nth-child(2) 选中的可能不是第二个 li,而是第二个子元素(如果第一个是 div),前提是那个 div 也得是第二个子元素。如果你只想在同类元素里数,得用 :nth-of-type。
表单验证神器 :valid/:invalid,用户填错都不用 JS 提醒
这玩意儿配合 HTML5 的表单验证属性,能让你少写好多 JS。input 有个 pattern 属性或者 type="email" 这种,浏览器会自动帮你验证格式对不对,然后用 :valid 和 :invalid 就能抓到状态。
<form class="signup-form">
<div class="form-group">
<input
type="email"
class="email-input"
placeholder="输入你的邮箱"
required
>
<span class="error-msg">邮箱格式不对啊朋友</span>
</div>
<div class="form-group">
<input
type="password"
class="pwd-input"
pattern=".{8,}"
placeholder="至少8位密码"
required
>
<span class="success-msg">密码长度够了</span>
</div>
</form>
/* 默认隐藏提示信息 */
.error-msg, .success-msg {
display: none;
font-size: 12px;
margin-top: 5px;
}
/* 当 input 值无效时,显示错误信息 */
.email-input:invalid + .error-msg {
display: block;
color: #e74c3c;
}
/* 当密码有效时,显示成功信息 */
.pwd-input:valid + .success-msg {
display: block;
color: #27ae60;
}
/* 还可以给边框染色 */
input:invalid {
border-color: #e74c3c;
background-color: rgba(231, 76, 60, 0.05);
}
input:valid {
border-color: #27ae60;
}
看到那个 + 号了吗?这是相邻兄弟选择器,意思是"紧接在 input 后面的 .error-msg"。这样连 JS 都不用写,用户一边输入,浏览器一边验证,CSS 一边改样式,体验丝滑得很。而且这完全是纯 CSS 实现的,性能比你写一堆 oninput 事件监听强多了。
伪元素根本就不是真实 DOM 节点
好了,现在进入玄幻区域。伪元素这玩意儿,真的是"无中生有"。你先记住一句话:伪元素是 CSS 凭空造出来的 DOM 节点,HTML 里根本不存在。
::before 和 ::after 是 CSS 凭空造出来的"幽灵孩子"
每个 HTML 元素(大部分,后面会说例外),不管你写没写,CSS 都当你有两个隐藏的插槽:一个叫 ::before,一个叫 ::after。::before 是在元素内容的最前面插入,::after 是在最后面插入。
<!-- 你写的 -->
<div class="box">我是内容</div>
<!-- CSS 伪元素造出来的结构大概相当于(只是比喻,真实DOM里并没有) -->
<div class="box">
<::before>伪元素内容在这里</::before>
我是内容
<::after>伪元素内容在这里</::after>
</div>
这两个"幽灵孩子"默认是行内元素(inline),但你可以给它们设置各种样式,什么宽高、背景、边框、定位,统统可以。这就给了 CSS 极大的施展空间。
.box {
position: relative;
padding: 20px;
background: #ecf0f1;
border-radius: 8px;
}
/* 在 box 内容前面加个引号装饰 */
.box::before {
content: '"';
font-size: 40px;
color: #bdc3c7;
position: absolute;
top: -10px;
left: 10px;
font-family: Georgia, serif;
}
/* 在 box 内容后面加个"查看更多"的箭头 */
.box::after {
content: '→';
margin-left: 8px;
color: #3498db;
transition: transform 0.3s;
}
/* hover 时箭头往右动一动 */
.box:hover::after {
transform: translateX(4px);
}
content 属性才是伪元素的灵魂,没它你写一百个 ::after 也白搭
这里我要用血的经验教训提醒你:如果你写了 ::before 或者 ::after 却忘了写 content 属性,那这个伪元素根本不会出现。
/* 错的 */
.box::before {
width: 10px;
height: 10px;
background: red;
/* 没有 content,啥也不显示 */
}
/* 对的,哪怕 content 为空字符串也行 */
.box::before {
content: ''; /* 空内容也可以,但至少得有 */
width: 10px;
height: 10px;
background: red;
display: inline-block; /* 行内元素设宽高要转一下display */
}
content 不只是能写字符串,还能干很多骚操作:
/* 用 attr() 读取 HTML 属性的值 */
a::after {
content: ' (' attr(href) ')'; /* 在链接后面显示实际的 URL */
color: #95a5a6;
font-size: 0.8em;
}
/* 用 counter() 实现自动编号 */
ol.custom-list {
counter-reset: item; /* 重置计数器 */
}
ol.custom-list li::before {
content: counter(item) '. '; /* 使用计数器 */
counter-increment: item; /* 计数器递增 */
color: #e74c3c;
font-weight: bold;
}
/* 甚至可以放 Unicode 字符 */
.warning::before {
content: '\26A0'; /* 警告符号 ⚠ */
color: #f39c12;
margin-right: 8px;
}
清除浮动老古董 clearfix 为啥非得靠 ::after?真相在这
还记得浮动布局时代的噩梦吗?父元素里的子元素都 float 了,父元素就塌了,高度变成0。以前人们用各种方法,什么额外加个空 div 啊,overflow:hidden 啊,但最优雅的还是 clearfix,核心就是 ::after。
/* 经典的 clearfix */
.clearfix::after {
content: ''; /* 必须要有,哪怕是空的 */
display: table; /* 或者 block,table 是为了兼容一些老版本IE */
clear: both; /* 清除左右浮动 */
}
/* 现在用 flow-root 更现代,但要兼容老浏览器还是得 clearfix */
.modern-clearfix {
display: flow-root;
}
原理很简单:::after 在父元素内容最后插入了一个看不见的块级元素,这个元素设置了 clear: both,它必须排在所有浮动元素后面,所以就把父元素的高度撑开了。这招妙就妙在不需要改动 HTML 结构,纯 CSS 解决,而且 ::after 不会出现在 DOM 里,不影响 JS 操作。
它们长得像双胞胎,但户口本差十万八千里
现在咱们来做个彻底的大对比,把这俩货的底细扒清楚。
伪类操作的是"已有元素的某种状态",伪元素是"凭空插入新内容"
这是本质区别。伪类像是一个筛选器,从已有的元素里挑出一些符合特定条件的;伪元素是一个构造函数,凭空造出新的内容盒子。
/* 伪类:从所有 a 标签里,挑出那些被访问过的 */
a:visited {
color: purple;
}
/* 伪元素:在 p 标签里造出一个只包含第一个字母的盒子 */
p::first-letter {
font-size: 3em;
float: left;
margin-right: 8px;
color: #2c3e50;
}
语法上冒号数量不是玄学:一个冒号是伪类,两个是伪元素(CSS3 规范)
这条是硬性规定。CSS3 为了区分这两者,规定伪元素都用双冒号 ::,伪类用单冒号 :。
/* 伪类家族(单冒号) */
:hover
:focus
:active
:first-child
:nth-child(3)
:not(.exclude)
:is(h1, h2, h3) /* 新的 :is 也是伪类 */
/* 伪元素家族(双冒号) */
::before
::after
::first-line /* 第一行文字 */
::first-letter /* 第一个字母 */
::selection /* 用户选中的文字 */
::placeholder /* input 的占位符文字 */
但是,历史上有些伪元素(before 和 after)在 CSS2 里是用单冒号的,所以为了兼容老的浏览器和代码,现代浏览器通常也接受 :before 这种写法,不会报错。但你自己写的时候,务必用双冒号,这是为了可读性和规范性,而且一些新的伪元素(比如 ::selection)是不支持单冒号的。
浏览器开发者工具里看结构:伪类高亮原元素,伪元素单独列一行
打开 F12,这是最能直观感受到区别的方法。当你选中一个元素,如果是伪类在生效,你看到的是那个元素本身样式变了,元素结构没变。但如果是伪元素,你会在 Elements 面板里看到 ::before 或者 ::after 单独列在元素下面,就像它真的有个子元素一样。
而且伪元素在开发者工具里是可以单独勾选显示/隐藏样式的,就像操作真实 DOM 一样,但你右键检查它,是定位不到具体 HTML 源码的,因为它根本就不在源码里。
实际开发中那些骚操作全靠它们撑场面
理论知识说了一堆,来点实际的,看看这俩玩意儿在真实项目里能玩出什么花。
按钮 hover 加个小箭头?那是 ::after 在偷偷打工
这种设计很常见:按钮默认状态有个文字,鼠标一放上去,右边冒出个小箭头,或者小图标。
<button class="fancy-btn">
立即购买
</button>
.fancy-btn {
position: relative; /* 给伪元素定位当参照 */
padding: 12px 40px 12px 24px; /* 右边留出空间给箭头 */
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 25px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
}
/* 箭头用 ::after 做 */
.fancy-btn::after {
content: '→';
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
opacity: 0; /* 默认隐藏 */
transition: all 0.3s ease;
}
/* hover 时按钮变宽,箭头出现并往右移 */
.fancy-btn:hover {
padding-right: 48px; /* 给箭头让出更多空间 */
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.fancy-btn:hover::after {
opacity: 1;
right: 24px; /* 稍微移动一下,有动效 */
}
你会发现我没用 JS,甚至没在 HTML 里加那个箭头,全部 CSS 搞定。而且这段代码复用性极强,只要是 class 为 fancy-btn 的按钮,自动就有这个效果。
列表项前加彩色圆点不用 img?伪元素 content + background 搞定
UI 设计师有时候会给列表项前面设计各种花式 bullet,彩色的、方形的、带阴影的,甚至可能是小图标。你别傻傻地切图或者用 img 标签,伪元素三行代码搞定。
<ul class="feature-list">
<li>极速响应</li>
<li>安全可靠</li>
<li>24小时服务</li>
</ul>
.feature-list {
list-style: none; /* 干掉默认的黑点 */
padding-left: 0;
}
.feature-list li {
position: relative;
padding-left: 24px; /* 给伪元素留位置 */
margin-bottom: 12px;
line-height: 1.6;
}
/* 用 ::before 做彩色圆点 */
.feature-list li::before {
content: ''; /* 空内容,我们用背景色 */
position: absolute;
left: 0;
top: 0.5em; /* 垂直居中技巧 */
width: 8px;
height: 8px;
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
border-radius: 50%; /* 圆形 */
box-shadow: 0 2px 4px rgba(245, 87, 108, 0.3);
}
/* 甚至能给不同行的圆点不同颜色 */
.feature-list li:nth-child(1)::before {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}
.feature-list li:nth-child(2)::before {
background: linear-gradient(135deg, #30cfd0 0%, #330867 100%);
}
.feature-list li:nth-child(3)::before {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
}
看到 :nth-child 和 ::before 联手了吗?伪类负责选第几个 li,伪元素负责画圆点,完美配合。
表单输入框获得焦点时边框变色?:focus 一招鲜吃遍天
这个前面提过了,但值得再深化一下。现在流行那种"浮动标签"(Float Label)效果,就是输入框里原本有 placeholder,一点击输入,那个 placeholder 变小跑到上面去当 label。这可以用 :focus 配合 ::placeholder 和一些技巧实现,但更简单的是用 :focus 配合 sibling 选择器。
<div class="input-group">
<input type="text" id="username" placeholder=" ">
<label for="username">用户名</label>
<div class="border-line"></div>
</div>
.input-group {
position: relative;
margin-bottom: 20px;
}
.input-group input {
width: 100%;
padding: 10px 0;
font-size: 16px;
border: none; /* 干掉默认边框 */
border-bottom: 2px solid #ddd; /* 用底边框当线 */
outline: none;
background: transparent;
}
.input-group label {
position: absolute;
top: 10px;
left: 0;
font-size: 16px;
color: #999;
pointer-events: none; /* 让鼠标事件穿透 label,不然点不到 input */
transition: 0.3s ease all;
}
/* 底边框变色用伪元素做(当然直接给 input 加 border-color 也可以,但伪元素可以做动画效果) */
.input-group .border-line {
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 2px;
background: #3498db;
transition: width 0.4s ease;
}
/* 核心:当 input 获得焦点时,label 上浮变小,底边框变长 */
.input-group input:focus ~ .border-line,
.input-group input:not(:placeholder-shown) ~ .border-line {
width: 100%;
}
.input-group input:focus ~ label,
.input-group input:not(:placeholder-shown) ~ label {
top: -20px;
font-size: 12px;
color: #3498db;
}
这里 :focus 选中了获得焦点的 input,然后用 ~(通用兄弟选择器)选中后面的 .border-line 和 label,改变它们的样式。placeholder-shown 也是个伪类,表示当 placeholder 正在显示时(也就是 input 值为空时)。
奇偶行背景交替?:nth-child(even) 让表格瞬间高级感拉满
这个前面也提过,但实战代码可以更完善一点,特别是一些细节处理。
<table class="data-table">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>部门</th>
<th>薪资</th>
</tr>
</thead>
<tbody>
<tr><td>001</td><td>张三</td><td>技术部</td><td>15k</td></tr>
<tr><td>002</td><td>李四</td><td>设计部</td><td>12k</td></tr>
<tr><td>003</td><td>王五</td><td>技术部</td><td>18k</td></tr>
<tr><td>004</td><td>赵六</td><td>市场部</td><td>10k</td></tr>
<tr><td>005</td><td>钱七</td><td>技术部</td><td>20k</td></tr>
</tbody>
</table>
.data-table {
width: 100%;
border-collapse: collapse; /* 合并边框,避免双边框 */
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.data-table th,
.data-table td {
padding: 12px 16px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
/* 表头样式 */
.data-table thead th {
background-color: #f8f9fa;
font-weight: 600;
color: #495057;
text-transform: uppercase;
font-size: 0.85em;
letter-spacing: 0.5px;
}
/* 斑马纹:偶数行背景色不同 */
.data-table tbody tr:nth-child(even) {
background-color: #f8f9fa;
}
/* hover 效果:当前行高亮 */
.data-table tbody tr:hover {
background-color: #e9ecef;
cursor: default;
transition: background-color 0.2s;
}
/* 第一列加粗 */
.data-table td:first-child {
font-weight: 600;
color: #212529;
}
/* 最后一列(薪资)右对齐,并且绿色显示 */
.data-table td:last-child {
text-align: right;
color: #28a745;
font-weight: 500;
}
/* 可以结合伪元素给薪资前面加个 ¥ 符号 */
.data-table td:last-child::before {
content: '¥';
margin-right: 2px;
color: #6c757d;
}
这一段代码里,nth-child(even)、:hover、:first-child、:last-child 全用上了,表格瞬间就像个正经的企业级应用了,而且代码量极小。
踩过的坑比写的代码还多
好了,爽完了该泼冷水了。这俩玩意儿坑特别多,我踩过的坑能填满一个西湖。
写了 ::before 却不设 content,结果啥也不显示——别问,问就是默认值为空
这个前面强调过了,但我还得再说一遍,因为真的太容易忘了。特别是当你想用伪元素纯粹当做一个装饰性的色块或者形状时,你脑子里想的是"我要一个红色的方块",手一滑就写了宽高背景色,然后页面上一片空白,你怀疑 CSS 文件没加载,怀疑选择器写错了,怀疑人生,最后发现就是少了个 content: ''。
而且 content 的默认值确实是 none,不是空字符串。如果你写 content: none,在某些老浏览器里可能还有问题,所以记住:永远写 content: ''。
想用伪元素做绝对定位却忘了父元素 position:relative,元素直接飞出银河系
这是 CSS 新手必犯的错,不只是伪元素,所有绝对定位都这样。但伪元素特别容易中招,因为你觉得"我就加个小装饰",随手写个 position: absolute,结果它跑到页面左上角去了,或者干脆不见了。
/* 错的 */
.box {
width: 200px;
height: 200px;
background: #eee;
}
.box::after {
content: 'badge';
position: absolute;
top: -10px;
right: -10px;
/* 结果这个 badge 跑到 body 的右上角去了,因为 .box 没设 relative */
}
/* 对的 */
.box {
position: relative; /* 建立定位上下文 */
width: 200px;
height: 200px;
background: #eee;
}
.box::after {
content: 'badge';
position: absolute;
top: -10px;
right: -10px;
background: red;
color: white;
padding: 4px 8px;
border-radius: 4px;
}
永远记住:绝对定位元素会相对于最近的非 static 定位的祖先元素定位。如果你没给父元素设 relative/absolute/fixed/sticky,它就一路往上找,直到找到 body。
在 img、input 这些自闭合标签上用 ::before?浏览器:我不认!
这个坑隐蔽性极强。有些元素是置换元素(Replaced Element),比如 img、input、textarea、select、iframe。这些元素在渲染的时候,内容会被外部资源替换掉(比如图片文件、表单控件),它们本身没有"内容区域"的概念,所以伪元素(before/after)在它们身上是无效的。
/* 这些都不会生效 */
img::before {
content: '加载中...'; /* 没用 */
}
input::after {
content: '✓'; /* 没用 */
}
br::before {
content: '我无敌了'; /* 更没用 */
}
如果你确实需要在 input 或者 img 前面加东西,包一层 div:
<div class="input-wrapper">
<input type="text">
</div>
.input-wrapper {
position: relative;
}
.input-wrapper::before {
content: '🔍';
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
}
.input-wrapper input {
padding-left: 35px; /* 给图标留位置 */
}
伪类顺序乱写导致 :visited 样式失效?LVHA 原则早该刻进 DNA
如果你给链接写样式,涉及到 :link、:visited、:hover、:active,顺序写错了,有些效果会出不来。记住口诀 LVHA:
- Link(未访问链接)
- Visited(已访问链接)
- Hover(鼠标悬停)
- Active(鼠标按下)
/* 正确顺序 */
a:link { color: blue; }
a:visited { color: purple; }
a:hover { color: red; }
a:active { color: green; }
/* 如果写成这样,hover 和 active 可能就失效了 */
a:hover { color: red; }
a:link { color: blue; } /* 后面覆盖前面,hover 被覆盖了 */
原理是 CSS 的层叠和优先级。:hover 必须放在 :link 和 :visited 后面,:active 必须放在 :hover 后面。虽然现在 :link 用得少了(一般都直接写 a 标签选择器),但如果你严格区分未访问和已访问状态,这个顺序还是得注意。
让代码又快又稳的土味技巧
最后分享一些实战中攒下来的小经验,土,但是好用。
能用伪类实现交互就别急着上 JS,性能省一半
鼠标悬停显示下拉菜单?:hover 搞定。输入验证实时反馈?:valid/:invalid 搞定。当前页面导航高亮?:current(或者给 body 加 class 配合 .nav-item.active)搞定。
JS 操作 DOM 是有成本的,特别是事件监听、修改 class、重排重绘。能用 CSS 解决的,浏览器原生帮你优化好了,丝滑得很。
纯元素做装饰性图标,比切图快还省 HTTP 请求
简单的几何图形(三角形、箭头、分割线、小圆点),用伪元素 + CSS 画出来,不需要请求图片资源,渲染快,还能随意改颜色、大小,响应式也友好。
比如画个简单的三角形:
.arrow-down {
position: relative;
}
.arrow-down::after {
content: '';
position: absolute;
top: 50%;
right: 10px;
width: 0;
height: 0;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 8px solid #333;
transform: translateY(-50%);
}
比用 img src=“arrow.png” 香多了。
调试时给伪元素加个 border,不然你永远不知道它跑哪去了
伪元素看不见摸不着,出问题的时候特别难调。我的习惯是,写的时候先给它加个明显的边框或背景色:
.debug::before {
content: '';
border: 1px solid red; /* 调试时加上 */
/* ... 其他样式 */
}
确认位置对了再删掉。或者直接在开发者工具里勾选 ::before 的样式,给它加个 outline: 2px solid red !important;,立竿见影。
兼容性兜底:IE8 只认单冒号,但谁还在乎 IE8 啊(狗头)
如前面所说,如果你真的很不幸要兼容 IE8( condolences),记得 :before 和 :after 用单冒号。如果是现代浏览器,坚持用双冒号,这是语义正确的做法,而且一些新的伪元素(::selection、::placeholder、::backdrop 等)是不支持单冒号的,养成好习惯,省得以后改。
当你终于分清它们那天,组长说:现在流行用 Web Components 了……
就像你现在看到的这篇文章,从入门到入土,从 :hover 到 ::part(Web Components 里的伪元素,用来穿透 Shadow DOM styling),CSS 的世界一直在变。
但万变不离其宗,理解伪类是状态、伪元素是内容这个核心区别,你再去学什么新的选择器都能举一反三。哪怕以后真用上了 Tailwind 或者 CSS-in-JS,这些底层的概念依然是你的根基。
好了,扯了这么多,代码也贴了这么多,希望你看完这篇不像技术文章的技术文章,能真的把这俩"妖精"给驯服了。下次面试再被问到,别像我一样支支吾吾,直接甩出这段:“伪类是单冒号,操作已有元素的状态;伪元素是双冒号,凭空插入新内容。content 是伪元素的命根子,relative 是绝对定位的锚点。”
妥妥的。
至于组长说的 Web Components……害,先让我把这周的需求交了再说吧,谁爱学谁学去。
(全文完,代码拿去随便 copy,出了 bug 别找我,找就是浏览器兼容性 issues)
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
|---|---|---|
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。 通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 |
|
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

更多推荐



所有评论(0)