一文吃透-CSS-伪类从鼠标悬停到斑马纹表格的-30-个实战场景
一文吃透 CSS 伪类:从「鼠标悬停」到「斑马纹表格」的 30 个实战场景
0. 前言:为什么你总在写「多余」的 JS?
前端日常中,我们经常会写这样的代码:
// 给每行表格隔行变色
trs.forEach((tr, i) => tr.style.background = i % 2 ? '#fff' : '#f7f7f7');
// 给按钮换色
btn.addEventListener('mouseenter', () => btn.style.background = '#eee');
btn.addEventListener('mouseleave', () => btn.style.background = '#fff');
伪类(Pseudo-class) 的存在,就是为了让浏览器帮你干这些「体力活」——一行 CSS 顶替十几行 JS,性能更好、可维护性更高,还能天然支持「未来新增的元素」。
1. 伪类到底是什么?
W3C 原话:
A pseudo-class is a keyword that specifies a special state of the selected element(s).
关键词:状态。
伪类并不对应 HTML 中的真实 class,而是描述元素处于某种瞬时或结构上的状态时该如何渲染。
语法:在选择器后加 :
+ 关键字,如 a:hover
、li:nth-child(2n+1)
。
2. 伪类全家福(CSS Selectors Level 4 版)
类别 | 常用伪类 | 一句话记忆 |
---|---|---|
用户行为 | :hover :active :focus :focus-visible | 悬停 / 按下 / 聚焦 / 键盘聚焦 |
表单反馈 | :checked :disabled :valid :invalid :in-range :out-of-range … | 勾、禁、对、错、范围内、超范围 |
结构 / 序号 | :first-child :last-child :nth-child() :nth-of-type() :only-child … | 第 n 个、倒数第 n 个、同类第 n 个 |
超链接历史 | :link :visited | 未访问 / 已访问 |
其他 | :target :root :empty :not() | 锚点、根节点、空节点、反选 |
3. 实战:不用伪类 vs 用伪类
3.1 隔行变色(斑马纹)
/* ==== 1 行 CSS ==== */
.striped-table tbody tr:nth-child(2n) { background:#f7f7f7; }
不用伪类:
// 还要考虑动态新增、排序、翻页……
document.querySelectorAll('tbody tr').forEach((tr,i)=>{
tr.style.background = i%2 ? '#f7f7f7' : '';
});
3.2 自定义复选框
/* 隐藏原生框,用兄弟元素画钩 */
input[type=checkbox] { display:none; }
input[type=checkbox] + label::before {
content:'';
display:inline-block;
width:14px; height:14px;
border:1px solid #999;
margin-right:6px;
}
input[type=checkbox]:checked + label::before {
background:#1890ff url(tick.svg) center/10px no-repeat;
}
不用伪类: 得监听 change
事件,手动增删 class。
3.3 聚焦输入框高亮
.form-input:focus { border-color:#1890ff; box-shadow:0 0 0 2px rgba(24,144,255,.2); }
不用伪类: 得 onfocus
/onblur
来回改行内样式。
3.4 数字输入框范围提示
input[type=number]:in-range { border-color:#52c41a; }
input[type=number]:out-of-range { border-color:#ff4d4f; }
不用伪类: 得 oninput
时读 value
再比大小。
3.5 空购物车提示
.cart ul:empty::after { content:'购物车是空的,快去逛逛吧!'; color:#999; }
不用伪类: 得 JS 数 li
个数再插入/移除提示节点。
3.6 锚点定位高亮
section:target { scroll-margin-top:80px; background:#fffbe6; }
点击目录跳转时,目标章节自动黄条高亮,无需任何脚本。
3.7 反选过滤
button:not([disabled]) { cursor:pointer; }
/* 只给「可用」按钮加手型,禁用按钮忽略 */
4. 容易踩的坑
序号伪类是对所有兄弟节点计数,而不是「同类名」。
ul li:nth-of-type(2n) /* 同类标签第 n 个,不受其他元素干扰 */
:hover
在触屏设备上可能「粘滞」,要加@media(hover:hover)
做区分。:visited
为了隐私,能改的属性极少(color/border-color/background-color 且透明度必须为 1)。:focus
与:focus-visible
区别:后者只在「键盘tab」时出现,鼠标点击不显示,适合做「蓝框」。
5. 性能 & 可维护性
- 浏览器内部对伪类做了高度优化,比手动 DOM 遍历快得多。
- 样式与行为分离,产品要改「斑马色」设计师直接改 CSS,无需打扰 FE 重构。
- 动态内容(ajax 翻页、拖拽排序)也不用重新执行染色脚本。
6. 彩蛋:纯 CSS Toggle 开关
.toggle { display:none; }
.toggle + label { position:relative; width:40px; height:20px;
background:#ccc; border-radius:10px; cursor:pointer; }
.toggle + label::after { content:''; position:absolute; left:2px; top:2px;
width:16px; height:16px; background:#fff; border-radius:50%; transition:.3s; }
.toggle:checked + label { background:#1890ff; }
.toggle:checked + label::after { transform:translateX(20px); }
<input class="toggle" id="t" type="checkbox"><label for="t"></label>
零 JS 即可得到 Material 风格滑动开关。
7. 速查海报(保存即可)
场景 | 一行代码 |
---|---|
隔行变色 | tr:nth-child(2n){background:#f7f7f7} |
首项不同 | li:first-child{font-weight:bold} |
末项补灰线 | li:last-child{border-bottom:none} |
仅一项时隐藏「删除」按钮 | ul li:only-child .del{display:none} |
输入非法红框 | input:invalid{border-color:#ff4d4f} |
鼠标悬停放大 | img:hover{transform:scale(1.1)} |
键盘聚焦蓝环 | button:focus-visible{outline:2px solid #1890ff} |
8. 结语:把 JS 留給业务,把状态交給 CSS
伪类不是「小语法糖」,而是声明式编程在前端的最好体现。
下次接到「奇偶行变色」「按钮按下换色」「表单验证红框」等需求,先想想:
能不能一行伪类解决?
能,你就省下:
- 至少 10 行 JS
- 一次 DOM 查询
- 未来维护时踩坑的风险
把省下的时间用来写业务逻辑,或摸鱼,也是极好的。
9. 参考资料 & 延伸阅读
- MDN
:nth-child
可视化工具 - Can I use – 查询兼容性
- 《CSS 揭秘》Chapter 3「结构布局」
(完)