抽屉导航必须用包裹,否则焦点丢失、aria失效;需同步js状态与css类,用transform而非left/margin动画;严格校验遮罩层点击目标,支持escape/tab焦点管理。

抽屉导航的 DOM 结构必须用 <aside></aside> + <nav></nav> 组合
浏览器对语义化结构有隐式行为,比如屏幕阅读器会跳过未正确包裹的导航内容。单纯用 <div class="drawer"> 会导致键盘焦点丢失、<code>aria-expanded 属性失效,且 Safari 在 iOS 上可能忽略 transform 动画触发条件。
正确结构应为:
<aside id="drawer" role="navigation" aria-expanded="false"><nav><a href="/home">首页</a>
<a href="/about">关于</a>
</nav></aside>
-
role="navigation"是必需的,否则aria-expanded不被识别 - 不要把
<nav></nav>拆到<aside></aside>外层——这会破坏 ARIA 树层级 -
aria-expanded必须同步控制 JS 状态和 CSS 类(如is-open),不能只靠 class 切换
transform: translateX() 比 left 或 margin-left 更可靠
用 left 触发重排(reflow),动画卡顿明显;margin-left 在 flex 容器中行为不稳定,尤其当父容器设了 overflow: hidden 时可能裁剪不干净。
关键 CSS 要点:
.drawer {
position: fixed;
top: 0;
left: 0;
width: 280px;
height: 100vh;
transform: translateX(-100%);
transition: transform 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.drawer.is-open {
transform: translateX(0);
}
- 必须加
cubic-bezier(0.22, 0.61, 0.36, 1),原生ease在移动端回弹感太强 - 不要用
visibility: hidden配合opacity——它无法阻止键盘焦点进入隐藏区域 - 若需支持 Safari 15.4 以下版本,需额外加
will-change: transform防止闪屏
点击遮罩层关闭时,event.target 必须严格校验
常见错误是监听遮罩层 click 后直接关闭,结果用户点到内部链接或按钮时也触发关闭,导致跳转中断。
安全做法是:
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
closeDrawer();
}
});
- 不能用
e.currentTarget === e.target,因为事件冒泡后currentTarget始终是 overlay - 如果抽屉里用了
<button></button>或自定义组件,需确保它们不继承pointer-events: none - 移动端要额外处理
touchend,iOS Safari 对click有 300ms 延迟,仅靠 click 会响应滞后
键盘操作必须支持 Escape 和 Tab 循环
抽屉打开后,焦点若仍留在原页面按钮上,视障用户无法操作;若不限制 Tab 范围,焦点会跑到抽屉外甚至 body 底部。
实操要点:
- 打开时用
drawer.focus()或firstLink.focus()主动聚焦第一个可交互元素 - 关闭时恢复焦点到触发按钮(需提前缓存
triggerEl) - 用
document.addEventListener('keydown', ...)监听Escape,但注意避免多次绑定(每次打开都加监听器会导致重复执行) - Tab 循环需手动实现:捕获
Tab键,检测是否到达最后一个元素,是则focus()第一个,反之亦然
aria-expanded 同步,或漏掉一次 focus() 恢复,整套交互在辅助技术下就形同虚设。











