CSS伪元素实战:用before-after轻松添加图标、文字与装饰(附避坑
- CSS伪元素实战:用before-after轻松添加图标、文字与装饰(附避坑指南)
- 为什么设计师总说“这个小图标加个伪元素就行”?
- before 和 after 到底是啥?先给它们上户口
- 浏览器如何渲染伪元素:一张图看懂
- 从零开始写伪元素:语法全家桶
- 1. 最简“小箭头”
- 2. 彩色“新”标签
- 3. 自定义复选框(无需图标库)
- 不只是装饰!用伪元素插入文字的实用场景
- 场景 1:表单必填红点
- 场景 2:价格单位
- 场景 3:英文段落自动加“”
- 让图标飞起来:SVG、base64、mask 三种姿势
- 姿势 1:行内 SVG + urlencode
- 姿势 2:base64 大图
- 姿势 3:mask + 单色图标(最优雅)
- 动态交互:hover、focus 也能玩伪元素
- 案例 1:按钮悬浮波纹
- 案例 2:输入框聚焦时显示密码强度
- 伪元素有哪些“做不到”的红线
- 性能与可访问性:让老板和用户都挑不出刺
- 真实项目三连击:表单、按钮、分割线
- 1. 表单验证错误提示
- 2. 按钮右侧“外跳”小箭头
- 3. 高级感分割线
- 遇到不显示?五大排查口诀
- 高手技巧:变量、计数器、动画一起嗨
- CSS 变量实时换色
- 计数器自动编号
- 伪元素 + 动画实现“打点”加载
- 雷区总结:z-index、继承、响应式
- 设计师改需求?三行代码不改 DOM
- 结语:把“伪”玩成“真”功夫
CSS伪元素实战:用before-after轻松添加图标、文字与装饰(附避坑指南)
“哥,这个箭头别切图了,给
::before写两行 CSS 就行。”
—— 十年前,我第一次听到这句话时,内心是崩溃的:两行?你咋不上天呢?
十年后,我把这句话原封不动送给了实习生,看着他瞳孔地震的表情,仿佛看见了当年的自己。
如果你也经历过“设计师嘴里的‘简单’= 前端眼里的‘玄学’”,那么今天这篇超长干货,就是来还债的。
为什么设计师总说“这个小图标加个伪元素就行”?
因为——他们真的不用写代码。
伪元素就像 CSS 界的“影分身”:不占用 DOM 节点,却能在页面上蹦跶得比谁都欢。对设计师来说,它是“不破坏结构”的万能胶;对前端来说,它是“不重启服务”的速效救心丸。
但影分身也有翻车现场:
- 图标死活不显示,一查
content忘写了 - 悬浮动画卡成 PPT,发现
will-change没加 - 屏幕阅读器把“伪”内容当成真内容读出来,用户一脸懵
本文目标:让你从“伪元素恐惧症”患者,变成“遇事不决先伪元素”的老司机。全程无尿点,代码管饱,坑位标红。
before 和 after 到底是啥?先给它们上户口
官方说法:::before创建一个伪元素,作为选中元素的第一个子节点;
`::after** 同理,不过是最后一个子节点。
人话翻译:
浏览器在渲染时,偷偷给你塞了一个不存在的 span,你可以随意打扮它,但它永远不会出现在 DOM 树里。
/* 最简骨架 */.tip::before{content:"";/* 不写这句,后面全白给 */display:inline-block;/* 默认是行内,想调宽高先转块 */width:16px;height:16px;background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2L2 22h20L12 2z'/%3E%3C/svg%3E")center/contain no-repeat;}要点:
content必须出现,哪怕空字符串。- 伪元素默认
display: inline,改宽高前记得先转inline-block/block。 - 伪元素无法被 JS 直接选中,但可以通过操作父元素属性,间接传参(后面有黑科技)。
浏览器如何渲染伪元素:一张图看懂
真实 DOM: <button class="save">保存</button> 渲染树: button.save ├─ ::before (你画的小图标) ├─ "保存" (真实文本节点) └─ ::after (你画的加载动画)浏览器把伪元素当成普通盒子参与布局、重绘、合成,所以:
- 它们会触发
reflow吗?会,如果改了尺寸位置。 - 会进合成层吗?加
will-change: transform就能直升 GPU。
性能口诀:“动画用 transform,内容用 content,别动 top/left 飙高 reflow。”
从零开始写伪元素:语法全家桶
1. 最简“小箭头”
.next::after{content:"→";/* 纯字符 */margin-left:4px;color:#666;transition:transform .2s;}.next:hover::after{transform:translateX(4px);}2. 彩色“新”标签
.badge::before{content:"新";font-size:12px;background:#ff4757;color:#fff;padding:2px 4px;border-radius:4px;margin-right:6px;vertical-align:middle;}3. 自定义复选框(无需图标库)
/* 隐藏原 checkbox */.checkbox input{position:absolute;clip:rect(0 0 0 0);}.checkbox::before{content:"";display:inline-block;width:18px;height:18px;border:2px solid #ddd;border-radius:4px;margin-right:8px;vertical-align:middle;transition:all .2s;}.checkbox:has(input:checked)::before{/* :has 已全平台支持 */background:#1890ff;border-color:#1890ff;}.checkbox:has(input:checked)::after{/* 对勾 */content:"";position:absolute;left:5px;top:9px;width:6px;height:10px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg);}不只是装饰!用伪元素插入文字的实用场景
场景 1:表单必填红点
.required label::after{content:" *";color:#e60012;font-size:14px;margin-left:2px;}优点:
- 不改动 HTML,产品经理后期想改“*”成“(必填)”只需改 CSS。
- 屏幕阅读器默认不会读出
::after内容,无障碍风险可控(后面有补救方案)。
场景 2:价格单位
.price::before{content:"¥";font-weight:normal;margin-right:2px;}场景 3:英文段落自动加“”
.quote::before{content:open-quote;}.quote::after{content:close-quote;}让图标飞起来:SVG、base64、mask 三种姿势
姿势 1:行内 SVG + urlencode
.icon-del::before{content:"";width:20px;height:20px;display:inline-block;background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23ff4757' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E")center/contain no-repeat;}优点:单色图标可随 CSSfill换色;缺点:urlencode 手酸。
姿势 2:base64 大图
.logo::before{content:"";width:120px;height:40px;background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAAoCAMAAAA7zkTfAAAAA3NCSVQICAjb4U/gAAAACVBMVEUqKjD///+QkJD///+aREJTAAAAA3RSTlMZAKw8jRJoAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M1cbXjLgAAABZ0RVh0Q3JlYXRpb24gVGltZQAwOS8xMi8xMdPZ/j8AAAErSURBVHhe7dgtC8JAGAXg/1+hhMTCQmxsbOwHEAsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL///5g6QAAAQl0RVh0Z2VuZXJhdGVkLWJ5PgZg+FHyAAAACnRFWHRmb3JtYXQAQ1NWPgAAAAxRFWHRpcGMAdGV4dAAAAAAACG1zZ2EAATAAAAANc2FyY2gBAAAAEAAAAYB2S2dIAAAAbUlEQVQYlWNgGAUkgv8ZGP7/hxmZgRXC5AhGhkYQZQBT5QjSBEVRDCMN0BTVMEwDNEU1DNMATVENwzRAU1TDMEw0RVUMw0RTVMUwTDRFVQzDRFNUpWkYJqCqMIwCUlU1jAJSlWUYBYQqHjAwAAAA//8FABu4IgUqtl0hAAAAAElFTkSuQmCC")center/contain no-repeat;}适合彩色 logo,但体积膨胀 1/3,慎用。
姿势 3:mask + 单色图标(最优雅)
.icon-heart::before{content:"";display:inline-block;width:20px;height:20px;background:currentColor;/* 颜色随父级文字走 */-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/%3E%3C/svg%3E")center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'/%3E%3C/svg%3E")center/contain no-repeat;}换色只需color: red;一行搞定,夜间模式爽到飞起。
动态交互:hover、focus 也能玩伪元素
案例 1:按钮悬浮波纹
.ripple{position:relative;overflow:hidden;}.ripple::after{content:"";position:absolute;left:var(--x,50%);top:var(--y,50%);width:0;height:0;border-radius:50%;background:rgba(255,255,255,.5);transform:translate(-50%,-50%);transition:width .6s,height .6s;}.ripple:hover::after{width:300px;height:300px;}JS 负责把鼠标坐标写进 CSS 变量,纯 CSS 实现波纹,零 DOM 污染:
document.querySelectorAll('.ripple').forEach(btn=>{btn.addEventListener('mouseenter',e=>{constrect=btn.getBoundingClientRect();btn.style.setProperty('--x',`${e.clientX-rect.left}px`);btn.style.setProperty('--y',`${e.clientY-rect.top}px`);});});案例 2:输入框聚焦时显示密码强度
.pwd{position:relative;}.pwd::after{content:"弱";position:absolute;right:12px;top:50%;transform:translateY(-50%);font-size:12px;color:#fff;background:#ff4757;padding:2px 6px;border-radius:4px;opacity:0;transition:opacity .3s;}.pwd:has(input:focus)::after{opacity:1;}/* 强度颜色用 JS 动态改 --level 变量 */.pwd::after{content:var(--level,"弱");background:var(--color,"#ff4757");}伪元素有哪些“做不到”的红线
无法绑定独立事件
伪元素属于影子分身,JS 监听器永远抓不到它。曲线救国:给父元素绑事件,通过e.offsetX判断点是否在伪元素区域。无法被屏幕阅读器天然识别
如果content是纯字符,读屏软件会读出来;如果只想装饰,用role="presentation"或者aria-hidden="true"让父元素对辅助技术隐藏。无法通过搜索引擎抓取
SEO 关键文字千万别放伪元素,否则爬虫看不见,排名哭晕。无法继承某些属性
::before不会继承父级的text-decoration、outline,需要显式重写。
性能与可访问性:让老板和用户都挑不出刺
性能
伪元素本身不占 DOM,但滥用动画同样会拖垮帧率。记住“动画三剑客”:transform、opacity、filter。改颜色直接改background而不是换整张图。可访问性
如果伪元素只是装饰,给父元素加aria-hidden="true";
如果伪元素承载重要信息(比如“必填”星号),务必在真实 DOM 里留一份等价内容,或使用aria-label补充说明。
<label>用户名<spanaria-label="必填">*</span></label>真实项目三连击:表单、按钮、分割线
1. 表单验证错误提示
.input-box{position:relative;}.input-box.error::after{content:attr(data-msg);/* 把错误文本写在属性里,CSS 直接拿 */position:absolute;left:0;top:calc(100% + 4px);font-size:12px;color:#ff4757;white-space:nowrap;}input.addEventListener('invalid',e=>{input.closest('.input-box').classList.add('error');input.closest('.input-box').setAttribute('data-msg',input.validationMessage);});2. 按钮右侧“外跳”小箭头
.external::after{content:"";display:inline-block;width:12px;height:12px;margin-left:4px;background:currentColor;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M10 6v2H5v11h11v-5h2v6a1 1 0 01-1 1H4a1 1 0 01-1-1V7a1 1 0 011-1h6z'/%3E%3Cpath d='M21 5h-8a1 1 0 00-1 1v8a1 1 0 001 1h8a1 1 0 001-1V6a1 1 0 00-1-1zm-8 2h8v8h-8V7z'/%3E%3C/svg%3E")center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M10 6v2H5v11h11v-5h2v6a1 1 0 01-1 1H4a1 1 0 01-1-1V7a1 1 0 011-1h6z'/%3E%3Cpath d='M21 5h-8a1 1 0 00-1 1v8a1 1 0 001 1h8a1 1 0 001-1V6a1 1 0 00-1-1zm-8 2h8v8h-8V7z'/%3E%3C/svg%3E")center/contain no-repeat;}换主题色只需改父级color,箭头自动跟随。
3. 高级感分割线
.divider{display:flex;align-items:center;font-size:14px;color:#999;margin:24px 0;}.divider::before, .divider::after{content:"";flex:1;height:1px;background:linear-gradient(to right,transparent,#ccc,transparent);}.divider::before{margin-right:16px;}.divider::after{margin-left:16px;}遇到不显示?五大排查口诀
content没写或写错
浏览器把伪元素当空气。伪元素被父级
overflow: hidden裁掉
给父级加position: relative并调高z-index。层级地狱
伪元素默认z-index: auto,想盖在兄弟节点之上,给父级建新的层叠上下文:position: relative; z-index: 1;。行内元素改宽高失败
忘记display: inline-block直接width/height无效。内容里有特殊字符没转义
content: "\2192"别写成"→",某些编码下会乱码。
高手技巧:变量、计数器、动画一起嗨
CSS 变量实时换色
.btn::before{content:"";width:var(--size,20px);height:var(--size,20px);background:var(--brand,#1890ff);}JS 一行改主题:
document.body.style.setProperty('--brand','#52c41a');计数器自动编号
.wrap{counter-reset:step;}.step::before{counter-increment:step;content:counter(step,decimal-leading-zero);/* 01、02 */display:inline-block;width:24px;height:24px;line-height:24px;text-align:center;background:#1890ff;color:#fff;border-radius:50%;margin-right:8px;font-size:12px;}伪元素 + 动画实现“打点”加载
.loading::after{content:"…";display:inline-block;width:20px;text-align:left;animation:dot 1.5s infinite;}@keyframesdot{0%{content:".";}33%{content:"..";}66%{content:"...";}100%{content:".";}}雷区总结:z-index、继承、响应式
z-index 陷阱
伪元素层级受父级限制,别指望z-index: 999就能飞上天。先确认父级是否创建了新的层叠上下文。继承误区
font-size、color能继承,text-decoration、outline不会继承,需要显式重写。响应式适配
伪元素尺寸单位尽量用em或rem,跟随字体缩放;图标用mask方案,换色不改图,深夜加班少掉两根头发。
设计师改需求?三行代码不改 DOM
原需求:按钮文字前加“🔥Hot”
新需求:把“🔥”换成“新”
/* 旧 */.btn::before{content:"🔥Hot";font-size:12px;margin-right:4px;background:#ff4757;color:#fff;padding:2px 4px;border-radius:4px;}/* 新 */.btn::before{content:"新";/* 其他一行不动 */}老板看完只能吐槽:
“你就改了一个字,工时怎么还是 0.5 人日?”
—— 咱靠的不是代码量,是定位精准,刀快不流血。
结语:把“伪”玩成“真”功夫
伪元素就像 CSS 给你的隐形画笔:
- 不增 DOM,却能画龙点睛;
- 不改结构,却能瞬息百变;
- 写得好,白天少加班;
- 踩坑了,深夜火葬场。
希望今天这锅超大量老火靓汤,能让你下次听到“加个图标而已”时,嘴角上扬,键盘翻飞,三分钟交活,剩下五分钟泡咖啡。
记住:
“代码越少,头发越贵;伪元素用得妙,需求改动不烦恼。”
祝你编码愉快,我们江湖再见!
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐: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等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!