移动端无hover状态,应使用:focus-within配合可聚焦链接实现兼容方案,并用visibility/opacity/pointer-events组合控制显隐与可访问性。

hover 在移动端根本不会触发,别直接照搬桌面写法
很多开发者把 :hover 写在二级菜单上,以为鼠标移入一级项就展开,结果在 iOS 或安卓浏览器里点一下没反应、再点一下直接跳转——因为触摸设备没有持续的 hover 状态,:hover 只在短暂的“悬停模拟”期间生效(比如 Safari 的长按),不可靠。
真正能兼顾桌面和移动端的方案,是用 :focus-within 配合可聚焦元素(如 <a> 或带 tabindex 的容器),让键盘用户和触屏点击都能触发。前提是:一级导航项必须是可聚焦的链接,且结构要支持事件冒泡到父容器。
- 一级菜单项必须用
<a>标签,不能只用<div>+ JS 模拟 - 二级菜单需嵌套在一级
<li>或<nav>内部,确保:focus-within能捕获子元素获得焦点 - 给一级链接加
tabindex="0"不必要;只要它是标准<a href>,天然可聚焦
focus-within 触发后,二级菜单必须默认隐藏且可访问
:focus-within 本身不控制显隐,它只是条件。你得配合 display: none / opacity / visibility 才行。但这里有个关键矛盾:用 display: none 会让屏幕阅读器完全忽略内容;而全用 opacity: 0 又可能被误点(尤其移动端)。
推荐组合:visibility: hidden + opacity: 0 + pointer-events: none 作为默认状态;激活时改为 visibility: visible + opacity: 1 + pointer-events: auto。这样既保证可访问性(visibility 不影响语义树),又防止误触。
立即学习“前端免费学习笔记(深入)”;
.nav-item {
position: relative;
}
.nav-item ul {
position: absolute;
top: 100%;
left: 0;
min-width: 200px;
visibility: hidden;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, visibility 0.2s step-end;
}
.nav-item:focus-within ul,
.nav-item:hover ul {
visibility: visible;
opacity: 1;
pointer-events: auto;
}
键盘用户需要 tab 键顺序进入二级菜单,别卡死在一级项
仅靠 :focus-within 不够——当用户 tab 到一级链接后,按 Tab 键默认会跳到下一个一级项,而不是二级菜单里的第一个链接。必须让二级菜单中的第一个 <a> 在一级项获得焦点后,能被自然 tab 进入。
- 二级菜单的
<ul>必须放在一级<li>内部,不能脱离 DOM 结构浮动到 body 底部 - 不要给二级菜单整体设
tabindex="-1",那会切断 tab 流;应确保其内每个<a>都是自然可聚焦的(即有href属性) - 如果一级项是按钮(
<button>),需手动用 JS 在 focus 时.querySelector('a').focus(),但这就偏离纯 CSS 方案了
IE 和旧版 Safari 不支持 focus-within,得有降级策略
:focus-within 在 IE 中完全不支持,在 Safari ≤15.4 中存在 bug(子元素 focus 后父元素不触发)。如果你的项目还要兼容这些环境,不能只靠它。
最轻量的降级是加一层 JS 判断:检测是否支持 :focus-within,不支持就动态加 class(如 .js-focus-within),CSS 里写两套规则。但更实际的做法是——在不支持的浏览器中,退回到点击展开(用 onclick 或 aria-expanded 控制),毕竟响应式导航的核心诉求是「可用」,不是「纯 CSS」。
别为了兼容 IE 把整个交互逻辑压给 CSS;该用 JS 的地方,就老老实实用 addEventListener('focusin') 监听委托,比 hack 更稳。


















