子菜单不显示的主因是父级未设position: relative导致z-index失效;需同时满足父级relative定位、子菜单absolute+合理z-index,且避免overflow: hidden;移动端应改用:focus-within或checkbox模拟,注意ie11需js fallback并添加aria属性。

子菜单不显示,大概率是 z-index 和 position 没配对
纯 css 多级菜单最常卡在这一步:HTML 结构嵌套是对的,CSS 也写了 display: block,但鼠标悬停后子菜单就是不冒出来。根本原因不是“没写 hover”,而是父级没建立定位上下文,导致子菜单的 z-index 完全失效。
必须同时满足两个条件子菜单才能正常层叠显示:
- 父级(比如
.nav-item)要设position: relative—— 这是给子菜单提供定位参考系 - 子菜单(比如
.submenu)要设position: absolute+ 合理的z-index(通常 >1 即可) - 别在父级上乱加
overflow: hidden,它会直接裁掉溢出的子菜单
示例关键片段:
.nav-item { position: relative; } .submenu { position: absolute; top: 100%; left: 0; z-index: 10; display: none; } .nav-item:hover .submenu { display: block; }
:hover 在移动端失效,是因为没有触发“悬停”这个动作
桌面端靠鼠标移入触发 :hover,但手机和平板没有 hover 状态,:hover 伪类在触摸设备上默认只在点击后短暂生效(甚至不生效),导致二级菜单点一下就闪退或根本打不开。
真实可行的解法不是“加 JS 监听 click”,而是用更轻量的 CSS 方案:
立即学习“前端免费学习笔记(深入)”;
- 把触发逻辑从
.nav-item:hover .submenu改成.nav-item:focus-within .submenu,配合tabindex="0"让菜单项可聚焦 - 或者直接用
input[type="checkbox"]+label模拟开关(无 JS,兼容性好,适合静态站点) - 避免在移动端强行模拟 hover 效果,容易引发点击穿透或 focus 错乱
层级错乱时,先查 transform 和 will-change 创建的新层叠上下文
明明 z-index 数值够大,子菜单还是被盖住?很可能是某个祖先元素悄悄创建了新的层叠上下文(stacking context),把整个导航区域“封进”了一个独立的渲染层里——这时子菜单的 z-index 只在这个小范围内起作用。
高频罪魁祸首是这些属性:
- 任何带
transform的值(哪怕只是transform: translateZ(0)) -
will-change: transform或will-change: opacity -
opacity小于 1 -
Filter不为none
检查方法:在浏览器开发者工具中选中导航容器,看右侧 Computed 面板是否出现 Stacking Context 提示。真有,就删掉或重构那个触发属性。
IE11 下 :focus-within 不支持,得 fallback 到 JS 控制类名
现代写法依赖 :focus-within 实现键盘导航和触屏友好,但 IE11 完全不认这个伪类。如果项目还要求兼容 IE11,不能只靠 CSS。
最小侵入式补救方案:
- 保留 CSS 中的
.nav-item[aria-expanded="true"] .submenu规则 - 用极简 JS 绑定
click和keydown(空格/Enter),切换aria-expanded属性 - 务必加上
aria-haspopup="true"和aria-expanded="false",否则屏幕阅读器无法识别菜单状态
这步漏掉,对键盘用户和残障用户就是不可用的——而它往往比视觉效果更难被发现。