做前端的朋友应该都遇到过这个需求:页面里有个下拉菜单,旁边要放一个箭头指示方向,展开时箭头转180度,收起时转回来。很多人的第一反应是去找一张“下拉箭头.png”,或者用unicode字符“▾”“▼”凑合一下。但真正写多了就会发现,图片方案有三个绕不开的毛病:一是多一次网络请求,二是颜色和尺寸没法跟随主题动态变化,三是换肤的时候还要准备一套甚至好几套不同颜色的箭头素材。
其实这个需求用CSS伪元素来解决,干净利落,一个字符都不用切图。我在实际项目里用这套方案写过不少组件,包括下拉框、折叠面板、手风琴菜单、导航菜单的子级展开指示,全部是纯CSS搞定,没有一张图片。今天就把这套思路从头到尾拆一遍,包括最核心的边框三角形实现原理、伪元素和CSS变量的配合方法、展开状态切换的两种主流写法,以及我踩过的那些坑。
这个方案适合谁?刚接触CSS不久、想搞懂伪元素到底能干什么的新手可以看,因为我会把::before、::after的底层规则讲清楚;已经写了几年样式、平时习惯用背景图或字体图标的老人也可以看,因为里面有些关于状态控制、动画过渡和延迟交互的细节,常规文档里不太容易找到现成答案。
1. 内容整体设计与思路拆解
1.1 为什么是伪元素而不是图片或字体图标
先聊点实在的:选型为什么不直接用字体图标,比如FontAwesome里的fa-chevron-down?
字体图标确实能用,而且现代框架里用法很成熟。但它有几个隐性成本:图标字体的引入要么用CDN,要么打包进项目,整体体积不低;如果你只是需要一个小箭头就为它引入一整份图标字体,性价比有点低。图片方案的问题更直接——颜色、大小、旋转角度全被固定死在素材里,想让箭头跟随文字颜色变化,得准备多套图,费时费力。
CSS伪元素方案的优势在于:
- 零额外资源:不需要图片、不需要字体文件,浏览器原生渲染,没有网络请求。
- 代码即设计:颜色、尺寸、圆角、旋转角度全部由CSS控制,改一个变量全局生效,天然适配主题换肤。
- 状态切换干净:展开和收起就是transform:rotate(0deg)和rotate(180deg)之间的切换,过渡动画直接用transition搞定,丝般顺滑。
- 可访问性不落空:给菜单按钮加上aria-expanded属性,视觉上的箭头旋转和辅助技术读出的状态变化是同步的,不会出现“看起来展开了,读屏却说折叠了”的割裂。
因为这些原因,我在近期一个管理后台项目里,把所有的下钻箭头都统一改成了伪元素实现,改动量很小,带来的收益却很直接——控件的颜色能跟随主题色自动变化,夜间模式下再也不用单独准备白色箭头了。
1.2 隐藏在箭头背后的核心思路:三角形是用边框“挤”出来的
好,伪元素的优势说完了,现在要进入最关键的原理部分——“箭头是怎么画出来的”。
很多人第一次看到下面这段CSS都会愣住:
css复制.arrow {
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 8px solid #333;
}
一个宽高都是0的元素,怎么最后看起来是个三角形?关键在于边框的斜接行为。当元素尺寸为0时,四条边框会在中心点汇合,相邻边框之间会形成斜角相接,而border-top的宽度决定了三角形的高,border-left/right的宽度决定了底边的半宽。
你可以想象一个充气的气球,每一条边都在向中心“挤”,窄的那边挤得快,宽的那边挤得慢,最后露出来的就是宽边所在的三角形。CSS里就是利用这个原理,把不需要的边设为透明色,只留下一个方向的边有颜色,视觉上就是一个实心三角形了。
这个方案的优势是浏览器兼容性极好,IE8时代就有人这么写,到现在依然完全可用。但它也有个小缺点——三角形是实心的,如果想让箭头变成一个“带边框的空心三角”,就得改用另一个思路:用transform旋转一个正方形。
css复制.arrow {
width: 8px;
height: 8px;
border-right: 2px solid #333;
border-bottom: 2px solid #333;
transform: rotate(45deg);
}
这段代码画出来的是“大于号”形状(›),旋转45度后就是一个朝下的实心箭头轮廓,适合想要线框风格箭头、同时想保持细节轻量的场景。两个方案我后面都会展开讲,你完全可以根据产品风格选择。
1.3 为什么箭头要放在伪元素里而不是单独写一个span
这是很多新手容易忽略的问题。单独写一个span放箭头,不是不行,而是会给HTML模板增加无意义的节点,还会让DOM层级变深。伪元素的神奇之处在于,它渲染出来的样式和普通DOM元素一样,但它不属于DOM树——不会干扰语义化结构,也不会被爬虫误读为正文内容,更重要的是,屏幕阅读器不会把它读出来。
用伪元素承载纯装饰性的箭头,正好和“内容与表现分离”的原则对上了。箭头本身不携带任何语义信息,它只是视觉指示器——真正的语义是“展开”“收起”,这个信息应该通过aria-expanded属性来传递,而不是靠视觉箭头本身。所以,装饰性的东西交给表现层(CSS),语义性的东西交给文档层(HTML/ARIA),各司其职,代码的可维护性就上来了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节解析与实操要点
2.1 伪元素的基础规则:没写content就是空忙一场
先解决一个最常见的问题:为什么我给元素写了::before,页面上却什么都看不到?
十有八九是没写content属性。与伪元素绑定的content属性并非可选项——即使内容为空,也至少要写成content:""。这是新手最容易踩的第一个坑,我当年也在这里栽过跟头,写完样式刷新页面,一看,箭头没出来,然后检查半天才反应过来。
伪元素完整生效需要满足四个条件:
| 条件 | 说明 | 错误示范 |
|---|---|---|
| content有值 | 写上content:"",空字符串也可以 | 忘记写content |
| display可显示 | 默认是inline,块级特征需要手动设置 | 设了宽高却忘了display:block |
| 定位上下文 | 父元素要用position:relative,否则会飘走 | 伪元素absolute后找不到锚点 |
| 生成内容可见 | 没被overflow:hidden裁掉 | 父容器overflow设错导致箭头被裁剪 |
每一条都是血泪经验。尤其是第二条,很多人给伪元素设了width和height然后发现没反应,就是因为默认的display:inline不响应宽高。解决办法也很简单,加一个display:block或者直接就着inline-block用。
2.2 箭头方向设计:从实心三角到线框箭头
现在进入正题,画箭头。按前面讲的两个思路,我分别给出可复用的配方。
方案A:边框三角形,适合实心箭头
css复制.menu-trigger {
position: relative;
padding-right: 20px;
}
.menu-trigger::after {
content: "";
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 6px solid #333;
}
这里border-top用的是实体颜色,它决定了箭头的颜色和高度;border-left/right是透明色,宽度决定了箭头的底边宽。实际效果就是倒过来的三角形,视觉上像一个朝下的小箭头。
方案B:旋转正方形,适合线框箭头
css复制.menu-trigger::after {
content: "";
position: absolute;
right: 2px;
top: calc(50% - 4px);
width: 8px;
height: 8px;
border-right: 2px solid #333;
border-bottom: 2px solid #333;
transform: rotate(45deg);
}
这个方案的精髓是transform:rotate(45deg)加上border-right和border-bottom。原来一个正方形,只画右边和下边的边框,旋转45度后,右边框和下边框就刚好合成一个朝下的尖角。线宽由border的宽度控制,颜色由border-color控制,大小由width和height控制,自由度非常高。
两种方案怎么选?我的经验是:按钮风格扁平、图形简洁时用方案B,它更轻盈、更像现代UI里的箭头;需要高识别度、强存在感的场景,比如数据表格里的大下拉按钮,用方案A更稳。
2.3 用CSS变量管理箭头颜色,告别写死颜色
你可能会想:箭头颜色写死成#333,那切换主题怎么办?难道每次都要改CSS?
不需要。搭配CSS自定义属性(变量),伪元素同样可以读取,并且可以随主题切换实时变化。
css复制:root {
--menu-arrow-color: #333;
--menu-arrow-size: 6px;
}
.menu-trigger::after {
content: "";
width: 0;
height: 0;
border-left: var(--menu-arrow-size) solid transparent;
border-right: var(--menu-arrow-size) solid transparent;
border-top: var(--menu-arrow-size) solid var(--menu-arrow-color);
}
当你在根节点换了主题色值,所有下拉菜单的箭头会自动跟着变,不需要额外写任何覆盖规则。我把这个实践叫做“CSS变量驱动的伪元素样式复用”,尤其适合组件库和复用性要求高的系统。热搜词里也有“css 控制伪元素变量”,说明大家确实对这个痛点有共鸣。
如果你用的是方案B的线框箭头,变量的控制方式也类似,只是把border-color换成变量、宽度换成变量:
css复制:root {
--arrow-line-color: #555;
--arrow-line-thickness: 2px;
}
.menu-trigger::after {
width: 8px;
height: 8px;
border-right: var(--arrow-line-thickness) solid var(--arrow-line-color);
border-bottom: var(--arrow-line-thickness) solid var(--arrow-line-color);
}
2.4 flex布局下的箭头定位:别再靠margin硬怼了
很多实际场景里,下拉菜单的触发器是一个flex容器,按钮左边放文字,右边放箭头,垂直居中。这时候伪元素的定位不用走absolute那一套,直接把它当flex子项来排更省心。
html复制<button class="menu-trigger">
<span>请选择城市</span>
</button>
css复制.menu-trigger {
display: inline-flex;
align-items: center;
gap: 8px;
}
.menu-trigger::after {
content: "";
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 6px solid #333;
}
这样写的好处是,箭头自动和文字基线对齐,flex容器默认会拉伸伪元素的高度,所以一个单纯的inline-flex布局就能搞定居中问题,不用计算坐标。热搜词里反复出现“css flex布局子元素宽度自适应”,这里其实也体现了flex给伪元素带来的便利——伪元素作为匿名flex项,只要不给它显式宽度,就按内容宽度排,非常自然。
3. 实操过程与核心环节实现
3.1 一个完整下拉菜单的HTML结构搭建
理论到位,我们开始实操。目标:做一个纯CSS下拉菜单,包含:触发按钮、箭头指示、展开面板、点击外部收起。为了控制篇幅,核心逻辑里我用CSS加少量JavaScript配合实现交互,但箭头部分全程由伪元素承担。
HTML的结构如下:
html复制<div class="dropdown" id="dropdown">
<button class="dropdown__trigger" aria-expanded="false" aria-haspopup="true">
可选课程
</button>
<ul class="dropdown__menu">
<li>前端进阶</li>
<li>CSS架构</li>
<li>TypeScript实战</li>
<li>性能优化</li>
</ul>
</div>
注意按钮上我加了aria-expanded,后面JS会更新它的值,让屏幕阅读器也能感知菜单状态。视觉的箭头由::after生成,HTML里完全不需要写箭头节点。
3.2 箭头如何伴随展开状态旋转
接下来是重点中的重点:怎么让箭头在展开时自动转180度。
我见过不少人用class切换实现,比如给dropdown加一个.open类,然后写:
css复制.dropdown.open .dropdown__trigger::after {
transform: rotate(180deg);
}
这种写法完全没问题,也是主流方案。但更优雅的做法是借助aria-expanded属性选择器,连JS都不用额外加类,只要设置aria属性就行。CSS可以这样写:
css复制.dropdown__trigger::after {
content: "";
display: block;
width: 8px;
height: 8px;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: rotate(45deg);
transition: transform 0.3s ease;
}
.dropdown__trigger[aria-expanded="true"]::after {
transform: rotate(225deg);
}
注意这里的transform转换逻辑:初始状态是rotate(45deg),虚线尖角朝下;展开状态是rotate(225deg),也就是再转180度,尖角朝上。为什么不是rotate(45+180=225)对,就是这个值。用currentColor作为边框颜色也是一个小技巧——它自动继承文字颜色,连CSS变量都省了,反正箭头跟文字同色是绝大部分场景的默认需求。
如果用实心三角方案,初始状态不需要rotate,展开状态直接rotate(180deg)就够:
css复制.dropdown__trigger::after {
content: "";
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 6px solid currentColor;
transition: transform 0.3s ease;
}
.dropdown__trigger[aria-expanded="true"]::after {
transform: rotate(180deg);
}
由于border三角形本质上是个图形,旋转180度后就是朝上的三角形,效果等价于箭头翻转。
3.3 JS侧的状态同步逻辑
配合上面的CSS,JavaScript只需要负责切换aria-expanded属性,以及点击外部时关闭菜单:
javascript复制const dropdown = document.getElementById('dropdown');
const trigger = dropdown.querySelector('.dropdown__trigger');
const menu = dropdown.querySelector('.dropdown__menu');
trigger.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = trigger.getAttribute('aria-expanded') === 'true';
trigger.setAttribute('aria-expanded', String(!isOpen));
});
document.addEventListener('click', (event) => {
if (!dropdown.contains(event.target)) {
trigger.setAttribute('aria-expanded', 'false');
}
});
这段JS的核心思路是:CSS只认属性,JS只管属性。逻辑彻底分离,以后想做动画效果直接改CSS,完全不需要动JS。
3.4 给下拉面板加上淡入淡出和位移效果
光有箭头旋转还不够,面板的显隐如果太生硬,整体体验会扣分。我的做法是给面板加opacity和transform的过渡,同时用visibility来保证不可见时不会捕获点击事件。
css复制.dropdown__menu {
position: absolute;
top: calc(100% + 8px);
left: 0;
min-width: 160px;
background: #fff;
border: 1px solid #eee;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
padding: 8px 0;
opacity: 0;
visibility: hidden;
transform: translateY(-8px);
transition: opacity 0.25s ease,
transform 0.25s ease,
visibility 0.25s;
}
.dropdown__trigger[aria-expanded="true"] ~ .dropdown__menu {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
这样即使菜单处于隐藏状态,也绝对不会被用户点到,更不会挡住其他元素的点击区域,比单纯的display:none切换更平滑,又兼顾了实用性。
3.5 细节打磨:圆角、阴影、分隔线的完整形态
为了让整个菜单更真实可用,我给菜单项加了一些细节:
css复制.dropdown__menu li {
padding: 10px 16px;
font-size: 14px;
color: #333;
cursor: pointer;
transition: background 0.2s;
}
.dropdown__menu li:hover {
background: #f7f8fa;
}
.dropdown__menu li + li {
border-top: 1px solid #f0f0f0;
}
别小看这些细节。li + li的选择器可以防止第一项上面出现多余的分隔线,这是我在实际项目中经常看到有人写错的地方——直接用li加border-top,导致第一项上面也有一条线,视觉上很突兀。
3.6 过渡动画的进阶玩法:延迟关闭防止误触
实际开发菜单类组件时,经常会遇到一个交互难题:当鼠标从按钮移到菜单项上时,必须经过按钮和菜单之间的空隙,如果这时菜单立刻隐藏,鼠标还没到达菜单项,菜单就消失了,用户会以为是自己操作错了。
解决办法之一是给菜单增加transition-delay,让它在鼠标离开后延迟一小段时间再隐藏。CSS可以这样实现:
css复制.dropdown__menu {
transition:
opacity 0.25s ease 0s,
transform 0.25s ease 0s,
visibility 0s linear 0s;
}
.dropdown__trigger[aria-expanded="false"] ~ .dropdown__menu {
transition:
opacity 0.2s ease 0.15s,
transform 0.2s ease 0.15s,
visibility 0s linear 0.25s;
}
这段让菜单在展开时正常显示,收起时延迟0.15秒再执行隐藏,给鼠标留了一条“安全通道”。热搜词里写着“css hover延迟关闭”,这个思路正是解决这类交互问题的通用方案。当然如果你的组件用JS来控制显隐,也可以直接在JS里用setTimeout实现类似效果,但CSS方案更简洁,少一层状态管理。
4. 常见问题与排查技巧实录
4.1 伪元素不显示的排查路径
伪元素不显示,按照下面的顺序排查基本能定位:
- 检查content有没有写。这个占80%的原因。
- 检查display是不是inline,如果是,且你设了宽高,不生效很正常,改成block或flex。
- 检查position: absolute的父元素是不是设置了position: relative。否则伪元素会找最近的已定位祖先,一不小心就飘到别处去了。
- 检查父容器有没有overflow: hidden,有时候箭头被裁掉了,而不是没显示。
我把这个整理成一张速查表,方便你贴在工位旁边:
| 症状 | 最可能的原因 | 解决办法 |
|---|---|---|
| 完全看不到箭头 | content缺失 | 补上content:"" |
| 箭头显示但位置不对 | absolute定位锚点错了 | 给父元素设置position:relative |
| 箭头尺寸没变化 | display是inline | 改成block/inline-flex/flex |
| 箭头被裁剪 | 父级overflow:hidden | 调整overflow或沿容器边缘预留空间 |
| 箭头出现在错误位置 | top/left未配合transform居中 | 用top:50%;left:0;transform:translateY(-50%) |
4.2 为什么设置了border-top却整出了一片正方形
很多新手想把border-top做成三角形,却得到了一整块正方形或矩形。原因是其它三边框没设置成transparent,四条边框都渲染出来,看起来当然不是三角形。
css复制/* 错误示范:四条边框都有颜色 */
.arrow {
width: 0;
height: 0;
border-top: 10px solid red;
border-left: 10px solid blue;
border-right: 10px solid green;
border-bottom: 10px solid yellow;
}
这样写出来就是一个“方块”,因为四条边框都可见。三角方案的关键是只让目标方向的边框有颜色,其余方向的边框必须透明。
4.3 旋转中心不对,箭头绕晕了
用transform:rotate做箭头翻转时,有朋友发现箭头不是绕着自己中心转,而是绕着一个奇怪的点转。原因大概率是transform-origin没设置。虽然大多数情况下transform默认以元素中心为原点,但如果伪元素本身有位移或者尺寸不对称,就会出现偏离。
稳妥的做法是显式声明:
css复制.dropdown__trigger::after {
transform-origin: center center;
}
如果你是用前面的绝对定位加top:50%;translateY(-50%)的方式定位箭头,那在旋转之前要确保translateY(-50%)先执行,再叠加rotate,CSS里多个transform值是从右到左执行的,写的时候注意顺序。
这里有一个常见错误:transform:rotate(180deg) translateY(-50%)和transform:translateY(-50%) rotate(180deg)结果不一样。第一个是先转再移,第二个是先移再转。因为我们的定位依赖translateY,所以我建议把translateY写在前面,也就是transform:translateY(-50%) rotate(180deg),这样旋转时元素已经位于垂直居中位置,旋转中心也更接近视觉中心。
4.4 切换状态时箭头方向反了
有朋友遇到展开后箭头朝下了,收起反而朝上,方向恰好反了。这通常说明初始状态和展开状态的rotate值写反了。
检查一下你的初始状态:方案B的线框箭头是45度朝下,那么展开就要225度;如果你初始写的是-135度,那展开就该45度。记住一个规律:两个状态之间相差180度,方向就一定是反的。如果不确定初始角度,用浏览器DevTools实时改一下rotate值,看哪个角度看起来是朝下的,再反推另一个。
这个方法非常实用,我在写复杂交互的时候经常用DevTools的样式面板边调边看,比盲试快得多。
4.5 如何用DevTools验证伪元素的实际占位
最后分享一个调试技巧:在Chrome DevTools的Elements面板里,默认是看不到伪元素的。你需要先选中按钮元素,然后在Styles面板右下角有一个“::before”“::after”的切换按钮,点开就能看到伪元素生成的规则,还能实时编辑。同时在页面上按F12,选中元素后依次点击“布局-伪元素”,可以直接查看它的盒模型占位。
我调试这种伪元素箭头组件时,最常用的流程是:
- 选中触发按钮。
- 打开Styles面板,找到::after规则的transform和border属性。
- 边修改数值边看页面效果,确定最终参数。
- 再回DevTools把最终成形的样式复制到代码里。
5. 扩展变体:从下拉菜单到更多UI场景
5.1 折叠面板(手风琴)里的箭头复用
折叠面板的需求和下拉菜单几乎一致,区别是面板展开区域更大、结构更复杂。CSS部分是一样的。
html复制<div class="accordion-item">
<button class="accordion-trigger" aria-expanded="false">第一章节</button>
<div class="accordion-panel">…内容…</div>
</div>
css复制.accordion-trigger::after {
content: "";
width: 8px;
height: 8px;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: rotate(45deg);
transition: transform 0.3s;
}
.accordion-trigger[aria-expanded="true"]::after {
transform: rotate(225deg);
}
你看,代码几乎零成本复用。这也是我说“伪元素方案适合做组件库基础能力”的原因——一套写法,到处复用。
5.2 多级导航菜单的箭头指示
如果导航菜单会有二级、三级子菜单,箭头通常分为两种状态:一级菜单箭头表示“还有子菜单”,二级菜单如果不幸是侧边展开,箭头方向又要换成指向右侧。
这时候用伪元素加data属性驱动状态就很灵活:
css复制.nav-item__arrow::after {
content: "";
width: 6px;
height: 6px;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: rotate(-45deg); /* 指向右 */
}
.nav-item--open .nav-item__arrow::after {
transform: rotate(45deg); /* 指向下 */
}
通过切换rotate值,同一个箭头可以同时表示两个方向。这种细节灵活性,图片方案和字体图标方案都很难做到——图片只能换资源,字体图标只能换unicode,而伪元素改一个角度就完事了。
5.3 箭头与涟漪、波纹等动效的组合
热搜词里反复出现“css涟漪光圈扩散”“css波浪效果”“css动效样式库”,这些动效和伪元素箭头组合起来,可以做出很亮眼的菜单反馈。比如点击菜单按钮时,箭头本身不旋转,反而先出现一圈涟漪效果。
css复制.dropdown__trigger::before {
content: "";
position: absolute;
inset: 0;
border-radius: 50%;
background: rgba(0, 0, 0, 0.1);
opacity: 0;
transform: scale(0);
transition: transform 0.4s ease, opacity 0.4s ease;
}
.dropdown__trigger[aria-expanded="true"]::before {
opacity: 1;
transform: scale(1.2);
}
这里的::before负责涟漪,::after负责箭头,同一个元素的两个伪元素各司其职,完全不冲突。很多人可能没意识到,单个元素其实可以同时承载两个伪元素,这意味着一个按钮的反馈动效基本不需要额外的DOM节点来实现。
5.4 移动端适配要点
小程序和移动端页面里,这套方案同样适用。但有三个移动端的特殊问题需要留意:
- 点击态:手机上没有hover,但会有触摸反馈,建议配合:active给箭头加一个轻微的scale或亮度变化。
- 安全区域:如果菜单贴在底部,要考虑iPhone的小黑条,箭头位置避开底部感应区域。
- 点击穿透:如果菜单里加载了异步内容,展开瞬间被遮挡,箭头可能看不见,建议等数据渲染完成后再设置aria-expanded为true,或者给面板加一个非常短的过渡延迟。
热搜词里有“小程序苹果底部兼容css”,我的经验是给底部固定菜单加safe-area-inset-bottom,再把箭头往上偏移一小段距离,这样在全面屏手机上箭头不会被手势条挡住。
6. 实操总结:这套方案能走到多远的边界
最后集中回答一个关键问题:伪元素箭头方案到底适合用在哪些场景,以及它的边界在哪里。
它非常适合:下拉选择框、折叠面板、侧边栏导航、手风琴组件、树形控件的展开箭头、表格里的筛选下拉。这几个场景的共同点是结构简单、状态单一(展开/收起),不需要复杂的图形逻辑。
它不太适合的场景包括:需要多色渐变箭头、需要复杂纹理图标、需要动画帧序列的情况。伪元素本质上是简单的线性图形,画一个小尖角是它的舒适区,画一只带渐变的小鸟就明显超纲了。那种场景老老实实用SVG或字体图标更合适。
另外一个要点:代码的可维护性。如果项目里箭头很多,建议把它们抽成公共的CSS类,配合CSS变量实现全局可配置。我自己的一个做法是建一个arrow.css工具类:
css复制.arrow {
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 6px solid currentColor;
}
.arrow--up {
transform: rotate(180deg);
}
团队里其他人要用的时候,直接套类名就行,不用关心内部实现。这就是组件化的思维:哪怕只是一个小箭头,也值得用可复用的方式设计。
最后再分享一个小技巧:如果担心伪元素生成的箭头在高DPI屏幕上发虚,可以给border-width用0.5px这样的半像素值,配合border-radius微调圆角。不同屏幕的渲染效果千差万别,用真机多测几个设备比盲目改代码靠谱得多。
