必须手动同步aria-expanded属性:初始设为"false",js切换菜单显隐后立即用setattribute更新该值,否则屏幕阅读器读取错误状态。

点击后菜单不收起,aria-expanded 没同步更新
这是最常被忽略的细节:按钮的 aria-expanded 属性必须和菜单实际展开状态严格一致。屏幕阅读器、自动化测试、甚至部分浏览器内置的无障碍逻辑都依赖它。只靠 CSS 切换 display 或 max-height 不够,JS 必须手动同步这个属性。
- 初始化时就该设置
aria-expanded="false"(菜单默认收起) - 点击按钮后,先切换菜单可见性,再更新
button.setAttribute('aria-expanded', 'true')或'false' - 如果用
classList.toggle()控制样式类,别忘了同时操作aria-expanded
移动端点击空白处关闭菜单,但 click 事件监听位置错了
很多人把 document.addEventListener('click', ...) 直接写在按钮或菜单上,结果一点击菜单项自己就关了——因为事件冒泡到了 document,触发了关闭逻辑。正确做法是区分点击目标。
- 监听
document的click,但在 handler 里用event.target判断是否点击在菜单区域外 - 用
!menu.contains(event.target) && !toggleButton.contains(event.target)做条件 - 避免对
<a></a>标签内点击也触发关闭,否则跳转前菜单就收了
max-height 动画卡顿,高度值写死或设为 0 导致过渡失效
CSS 过渡 max-height 看似简单,但浏览器无法对 auto 做动画,设成固定像素又难适配不同内容。常见错误是直接写 max-height: 0; → max-height: 500px;,结果内容少时留白,内容多时截断。
- 更稳妥的做法是用
height: auto+overflow: hidden配合 JS 动态读取滚动高度 - 展开前先强制设
height: 0,再offsetHeight触发重排,再设height: menu.scrollHeight + 'px' - 或者干脆放弃 height 动画,改用
opacity+transform: scaleY(),性能更稳
键盘用户打不开菜单,Enter 和 Space 没绑定
仅支持鼠标点击不符合可访问性要求。原生 <button></button> 默认响应 Enter 和 Space,但如果用了 <div role="button"> 就得自己补全。
<ul><li>确保触发元素是语义化 <code><button></button>,不是 <div> 加 <code>role="button"
keydown 事件,对 event.key === 'Enter' 或 ' '(空格)执行相同切换逻辑scrollBy,需 event.preventDefault()
真要实现可靠响应式折叠菜单,重点不在“怎么让菜单动起来”,而在于状态同步、焦点管理、键盘路径和动画边界处理。漏掉任意一环,用户可能点开打不开、关不掉、看不见、摸不到。











