如何制作index.html中的多层嵌套菜单?
- 内容介绍
- 文章标签
- 相关推荐
本文共计955个文字,预计阅读时间需要4分钟。
HTML 本身不提供菜单组件,多级菜单本质上是嵌套的列表结构。
常见错误:把子菜单写成独立的 <div class="submenu"> 放在页面底部,再靠 CSS 定位过去。这会导致 tab 键跳过子项、aria-expanded 失效、移动端点击穿透。
正确骨架示例如下:
<nav aria-label="主菜单"> <ul> <li><a href="/home">首页</a></li> <li> <a href="/products" aria-haspopup="true" aria-expanded="false">产品</a> <ul class="submenu"> <li><a href="/products/web">Web 端</a></li> <li><a href="/products/mobile">移动端</a></li> </ul> </li> </ul> </nav>
CSS 控制显示/隐藏与层级定位
纯 CSS 实现展开收起,关键在父 <li> 的 hover/focus 状态联动子 <ul> 的 display 或 visibility。不要用 opacity: 0 单独隐藏——它仍占据布局空间且无法禁用交互。
立即学习“前端免费学习笔记(深入)”;
要点:
- 子菜单默认设为
position: absolute,并用top: 100%对齐父项底部 - 父
<li>必须设position: relative,否则绝对定位会相对于 body 偏移 - 用
:hover > .submenu, :focus-within > .submenu同时支持鼠标悬停和键盘聚焦(focus-within是关键) - 移动端需额外加
click切换逻辑,CSS 无法可靠响应 touch
JavaScript 补足可访问性与移动端交互
仅靠 CSS 的 hover 在触屏设备上完全失效,且键盘用户无法用空格/回车展开菜单。必须用 JS 监听 click 和 keydown 事件,并同步更新 aria-expanded 和 aria-hidden。
核心逻辑:
- 给带子菜单的
<a>绑定click,阻止默认跳转,切换aria-expanded值 - 监听
Enter和Space键,行为与 click 一致 - 子菜单显示时,用
focus()把焦点移到第一个可聚焦子项(如首个<a>) - 按
Escape关闭当前展开的子菜单并返回触发链接 - 避免对所有
<li>监听事件——只处理有aria-haspopup="true"的项
为什么不能直接用 <details><summary>?
<details> 语义是“可折叠内容区块”,不是导航菜单。浏览器对其 role 解析不统一:部分读屏器不识别其为菜单,aria-activedescendant 不生效,且无法原生支持方向键导航(↑↓→←切换子项)。更严重的是,<details> 内部不能放 <a> 作为 summary 文本(会触发两次点击),破坏导航意图。
若强行用,会出现:键盘用户 tab 到 summary 后无法用方向键进入子项、移动端点一次展开、再点一次才跳转链接、SEO 友好性下降(搜索引擎可能忽略 details 内容)。
真正省事的方案不存在。多级菜单的健壮实现,永远需要 HTML 结构 + CSS 定位 + JS 交互 + ARIA 属性四者对齐。漏掉任意一环,都会在某个用户群体中彻底失效。
本文共计955个文字,预计阅读时间需要4分钟。
HTML 本身不提供菜单组件,多级菜单本质上是嵌套的列表结构。
常见错误:把子菜单写成独立的 <div class="submenu"> 放在页面底部,再靠 CSS 定位过去。这会导致 tab 键跳过子项、aria-expanded 失效、移动端点击穿透。
正确骨架示例如下:
<nav aria-label="主菜单"> <ul> <li><a href="/home">首页</a></li> <li> <a href="/products" aria-haspopup="true" aria-expanded="false">产品</a> <ul class="submenu"> <li><a href="/products/web">Web 端</a></li> <li><a href="/products/mobile">移动端</a></li> </ul> </li> </ul> </nav>
CSS 控制显示/隐藏与层级定位
纯 CSS 实现展开收起,关键在父 <li> 的 hover/focus 状态联动子 <ul> 的 display 或 visibility。不要用 opacity: 0 单独隐藏——它仍占据布局空间且无法禁用交互。
立即学习“前端免费学习笔记(深入)”;
要点:
- 子菜单默认设为
position: absolute,并用top: 100%对齐父项底部 - 父
<li>必须设position: relative,否则绝对定位会相对于 body 偏移 - 用
:hover > .submenu, :focus-within > .submenu同时支持鼠标悬停和键盘聚焦(focus-within是关键) - 移动端需额外加
click切换逻辑,CSS 无法可靠响应 touch
JavaScript 补足可访问性与移动端交互
仅靠 CSS 的 hover 在触屏设备上完全失效,且键盘用户无法用空格/回车展开菜单。必须用 JS 监听 click 和 keydown 事件,并同步更新 aria-expanded 和 aria-hidden。
核心逻辑:
- 给带子菜单的
<a>绑定click,阻止默认跳转,切换aria-expanded值 - 监听
Enter和Space键,行为与 click 一致 - 子菜单显示时,用
focus()把焦点移到第一个可聚焦子项(如首个<a>) - 按
Escape关闭当前展开的子菜单并返回触发链接 - 避免对所有
<li>监听事件——只处理有aria-haspopup="true"的项
为什么不能直接用 <details><summary>?
<details> 语义是“可折叠内容区块”,不是导航菜单。浏览器对其 role 解析不统一:部分读屏器不识别其为菜单,aria-activedescendant 不生效,且无法原生支持方向键导航(↑↓→←切换子项)。更严重的是,<details> 内部不能放 <a> 作为 summary 文本(会触发两次点击),破坏导航意图。
若强行用,会出现:键盘用户 tab 到 summary 后无法用方向键进入子项、移动端点一次展开、再点一次才跳转链接、SEO 友好性下降(搜索引擎可能忽略 details 内容)。
真正省事的方案不存在。多级菜单的健壮实现,永远需要 HTML 结构 + CSS 定位 + JS 交互 + ARIA 属性四者对齐。漏掉任意一环,都会在某个用户群体中彻底失效。

