鼠标离开一级菜单时二级菜单闪退是因悬停路径中断,需确保子ul为li直接子元素、消除像素间隙、用transform+opacity替代display切换,并为移动端设计fallback。

鼠标一离开一级菜单项,二级菜单就闪退——这不是 bug,是 CSS :hover 的正常行为。根本原因只有一个:悬停路径中断,中间存在不可 hover 的空白区域。
li:hover > ul 失效的典型结构陷阱
很多人把子菜单写在父 <li> 外面,比如:
<li><a href="#">产品</a></li> <ul class="dropdown-content">...</ul>
这种结构下,li:hover > ul 根本不匹配,因为 ul 不是 li 的子元素。浏览器找不到关联关系,悬停自然断开。
- 必须让子
<ul>是父<li>的**直接子元素** - 禁止用
<div>包裹子菜单来“桥接”,那会破坏语义和可访问性(role="menu"无法正确关联) - 所有
<a>必须在<li>内,不能让<a>直接当<ul>的子元素
消除像素级悬停断裂间隙
即使结构正确,li:hover > ul 还是闪退?大概率是定位后留了 1px 空隙。浏览器一旦检测到鼠标经过非匹配区域,:hover 立即失效。
立即学习“前端免费学习笔记(深入)”;
- 子
<ul>必须设top: 100%,而非top: auto或依赖默认流计算 - 绝对定位的子
<ul>要加margin-top: -1px或父<li>加padding-bottom: 1px,把热区向下延伸 - 避免写
ul ul { margin-top: 2px }——这 2px 就是闪退元凶 - 父
<li>必须有position: relative,否则子<ul>的top: 100%会相对视口定位,错位风险极高
用 transform + opacity 替代 display: none
靠 display: none / block 切换,动画会卡顿,且容易因重排导致鼠标“漏触”。更稳的做法是保持 DOM 占位、仅控制可见性与交互能力。
- 初始状态:
opacity: 0; transform: scaleY(0); pointer-events: none; overflow: hidden; - 悬停激活:
opacity: 1; transform: scaleY(1); pointer-events: auto; - 过渡必须写全:
transition: opacity 0.2s ease, transform 0.2s ease;,不要只写transition: all - 禁用
visibility: hidden配合opacity——两者渲染时机不同,易造成瞬间不可点
移动端 fallback 必须提前设计
纯 :hover 在触摸设备上基本无效。iOS Safari 和 Android Chrome 对 :hover 的支持极不稳定,尤其在快速点击后。
- 别等上线才发现——从开发第一天起就要加
@media (hover: none)或@media (pointer: coarse)规则 - 移动端改用
:focus-within(需确保<a>可聚焦)或 JS 切换.is-open类 - 所有子菜单必须加
role="menu",触发项加role="menuitem",否则 VoiceOver/NVDA 无法识别导航层级
真正难的不是写出能动的菜单,而是让鼠标从一级文字边缘滑向二级左上角那不到 10px 的路径全程不掉线。这个距离里,box-sizing、border、line-height、z-index、isolation,任何一个都可能成为断点。动手前先 inspect 元素盒模型,比反复调 margin 更快。



















