最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
HTML多级菜单悬停和点击触发的可访问性统一
时间:2026-07-16 08:47:54 编辑:袖梨 来源:一聚教程网
:hover 在移动端和键盘用户中失效,因其仅支持鼠标悬停;需统一用 JS 管理 aria-expanded 状态,配合 aria-haspopup="menu"、role="menu" 等语义属性,确保三端交互状态一致且符合 WCAG 2.1。
为什么纯 :hover 在移动端和键盘用户面前失效
因为 iOS Safari、Android Chrome 真机不触发 :hover,且键盘用户(Tab 导航)根本无法“悬停”。:hover 是鼠标专属伪类,不是交互状态的通用表达。可访问性标准(WCAG 2.1)明确要求:所有功能必须支持键盘操作,且不能依赖单一输入方式。
常见错误是只写 .menu-item:hover .submenu,结果移动端点不开、盲人用户按 Tab 进入菜单项后子菜单不显示、屏幕阅读器读不出展开状态。
- 桌面端鼠标悬停 → 触发子菜单显示
- 移动端触摸点击 → 必须用 JS 切换
aria-expanded和show类 - 键盘用户按 Enter/Space → 同样需 JS 响应,同步更新
aria-expanded和视觉状态 - 子菜单内按 Tab 或方向键 → 需保持父菜单项
aria-expanded="true",否则焦点离开即收起
aria-expanded 和 aria-haspopup 怎么配对用才不翻车
这两个属性必须成对出现,且值严格对应 DOM 状态。仅加 aria-expanded 不加 aria-haspopup,或反过来,都会让屏幕阅读器误判为普通链接。
正确写法:
立即学习“前端免费学习笔记(深入)”;
<li class="menu-item"> <a href="#" aria-haspopup="menu" aria-expanded="false">产品</a> <ul class="submenu" role="menu" hidden> <li role="none"><a href="/desktop" role="menuitem">桌面端</a></li> </ul></li>
-
aria-haspopup="menu"表示该元素会打开一个菜单(不是true,也不是dialog) -
aria-expanded初始为"false",JS 展开时设为"true",收起时还原 -
hidden属性比display: none更语义化,且对辅助技术更友好 - 子菜单
<ul>必须加role="menu",每个子项<a>加role="menuitem"
桌面端悬停 + 键盘触发如何共存不冲突
关键不是“禁用悬停”,而是让悬停和键盘逻辑共享同一套状态管理。一旦用 JS 控制了 aria-expanded,就不要再让 CSS :hover 单独控制 display —— 否则状态撕裂:鼠标悬停显示了,但 aria-expanded 还是 false,屏幕阅读器完全懵了。
- 统一用 JS 控制显隐,CSS 只负责过渡动画(
opacity、transform) - 监听
mouseenter/mouseleave仅用于「预加载」或「延迟展开」,不直接改状态 - 键盘事件(
Enter、Space、ArrowDown)必须调用同一展开函数 - 移动端
click也走同一函数,避免三套逻辑 - 用
classList.toggle('is-open')+aria-expanded同步更新,CSS 用.menu-item.is-open .submenu控制显示
移动端点击后子菜单消失的真正原因
不是 JS 没绑对,而是点击后页面重绘或焦点丢失导致状态重置。典型场景:点击一级菜单 → 展开二级 → 点击二级中某个链接 → 页面跳转前 JS 没来得及清理 is-open 类,下次回来时残留状态干扰新交互;或者更隐蔽的:iOS Safari 在 click 后短暂触发 blur,导致 focusout 事件误收起菜单。
- 所有展开/收起操作必须带防抖(
clearTimeout当前 pending timer) - 在
click处理函数末尾加e.preventDefault()(如果链接是空href="#") - 对真实跳转链接(如
href="/product"),收起逻辑要放在beforeunload或干脆不收起——用户已离开,无需干预 - 用
pointerdown替代click可规避 iOS 的 click 延迟和 focus 冲突
最易被忽略的是:把悬停当成“默认行为”,而把键盘和触摸当成“降级补丁”。其实三者应共用同一状态源(JS 变量或 DOM 属性),:hover 只是其中一种触发途径,不是唯一依据。一旦状态源头分裂,可访问性就塌了一半。