汉堡按钮必须用语义化实现,设type="button"防提交,配合实时切换的aria-expanded与严格匹配id的aria-controls,禁用或模拟。

汉堡按钮的 HTML 结构必须包含语义化触发器与可访问性标记
纯 <div> 堆叠的“三横线”按钮无法被屏幕阅读器识别,也缺乏键盘焦点支持。正确结构需用 <code><button></button> 作为触发器,并通过 aria-expanded 和 aria-controls 显式关联菜单容器。
常见错误是把 aria-expanded 写死为 "true" 或漏掉 id 匹配:
<button aria-expanded="false" aria-controls="nav-menu">☰</button> <nav id="nav-menu"><ul>...</ul></nav>
-
aria-expanded必须随菜单开闭实时切换为"true"或"false" -
aria-controls的值必须严格等于目标nav的id,大小写和连字符都不能错 - 避免用
<a href="#"></a>模拟按钮——会触发页面跳转、破坏键盘导航流
CSS 实现折叠/展开动画时不能只依赖 display: none
display: none 会让元素完全脱离渲染流,导致 transition 无效;而单纯用 opacity 又无法阻止键盘焦点进入隐藏菜单。折中方案是组合使用 visibility、height 和 overflow。
关键点在于:菜单容器需有明确高度(哪怕初始为 0),且 overflow: hidden 配合 transition 才能实现平滑收放:
nav[aria-expanded="false"] {
height: 0;
visibility: hidden;
overflow: hidden;
transition: height 0.3s ease, visibility 0.3s;
}
nav[aria-expanded="true"] {
height: auto;
visibility: visible;
}
- 不要对
height: auto做 transition——浏览器无法计算起止值,动画会失效 - 若菜单项高度不固定,改用
max-height(设一个足够大的值如500px)配合overflow: hidden - 务必加
visibility控制可访问性:仅靠opacity: 0仍允许键盘聚焦
JavaScript 切换逻辑必须处理点击外区域关闭与 ESC 键响应
用户点击菜单外部或按 ESC 键时,若菜单仍保持展开,体验就断了。这需要监听 document 级事件,而非仅绑定按钮。
核心逻辑要区分事件来源,避免重复触发:
const btn = document.querySelector('[aria-controls]');
const menu = document.getElementById(btn.getAttribute('aria-controls'));
btn.addEventListener('click', () => {
const expanded = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', !expanded);
menu.setAttribute('aria-expanded', !expanded);
});
document.addEventListener('click', (e) => {
if (!btn.contains(e.target) && !menu.contains(e.target)) {
btn.setAttribute('aria-expanded', 'false');
menu.setAttribute('aria-expanded', 'false');
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && btn.getAttribute('aria-expanded') === 'true') {
btn.setAttribute('aria-expanded', 'false');
menu.setAttribute('aria-expanded', 'false');
}
});
- 判断点击是否在菜单外时,必须用
contains()而非e.target !== btn && e.target !== menu——子元素点击会被误判 -
Escape监听需全局注册,且仅在菜单展开时生效,避免干扰其他输入框 - 不要用
toggleAttribute()替代显式设置——IE 不支持,且无法保证字符串值为小写"true"/"false"
移动端触摸穿透问题常因 fixed 定位与 z-index 层级混乱引发
当导航菜单设为 position: fixed,且页面内容也有 fixed 元素(如页脚工具栏)时,iOS Safari 可能出现“点击穿透”:手指点在菜单上,实际触发了下方元素的 click。
根本原因是 iOS 对 fixed 元素的合成层处理异常,解决方案是强制创建独立渲染层:
- 给菜单容器添加
transform: translateZ(0)或will-change: transform - 确保菜单的
z-index明确高于所有其他fixed元素(比如设为1000,而页脚用999) - 禁用菜单区域的滚动穿透:在菜单展开时给
加overflow: hidden,但注意同时记录原始scrollY,关闭时恢复位置
这个细节在桌面端测试不出问题,只有真机调试时才暴露——别等上线后被用户反馈才排查。











