复制:hover状态样式及无障碍应用键盘导航下拉菜单适配问题
嘿,我刚好在做无障碍导航功能时踩过一模一样的坑!先给你明确一点:前端确实没法直接用JS触发原生的:hover伪类——Chrome DevTools的那个功能是浏览器内部暴露的调试接口,咱们业务代码调用不了,但用类模拟+键盘事件监听的方案完全能解决这个问题,而且这也是无障碍场景下的标准实践。
给你梳理下具体的实现步骤:
1. JS事件监听与类切换
针对包含子列表的根层级<li>元素,我们需要监听键盘事件来控制自定义类的添加/移除:
- 监听
keydown事件,当用户按下Tab键聚焦到这个<li>,或者按下ArrowDown键要展开子菜单时,给它添加一个自定义类(比如active-hover); - 当用户按下
Escape键关闭菜单,或者Tab键离开整个菜单区域时,移除这个类; - 额外处理方向键:如果用户用
ArrowUp/ArrowDown在子菜单里导航,要确保父<li>的激活类一直保留,直到离开菜单。
简单的代码示例:
// 获取所有带子列表的根li元素 const parentLis = document.querySelectorAll('li:has(> ul)'); parentLis.forEach(li => { li.addEventListener('keydown', (e) => { switch(e.key) { case 'Tab': // Tab进入时添加类,离开时(通过blur事件)移除 li.classList.add('active-hover'); break; case 'Escape': li.classList.remove('active-hover'); // 同时让焦点回到父li,提升无障碍体验 li.focus(); break; case 'ArrowDown': e.preventDefault(); li.classList.add('active-hover'); // 自动聚焦第一个子菜单元素 li.querySelector('ul li:first-child')?.focus(); break; } }); // 当元素失去焦点时移除类 li.addEventListener('blur', () => { // 加个延迟,避免子元素聚焦时误触发 setTimeout(() => { if(!li.contains(document.activeElement)) { li.classList.remove('active-hover'); } }, 100); }); });
2. 样式同步
直接把原来:hover的样式复制一份,绑定到我们自定义的类上,同时结合:focus-within伪类(这个伪类会在元素本身或其子元素获得焦点时触发),让视觉反馈更完整:
/* 原来的hover样式 */ li:hover { background-color: #f0f0f0; color: #333; } /* 模拟hover的自定义类 + 焦点状态同步 */ li.active-hover, li:focus-within { background-color: #f0f0f0; color: #333; } /* 子菜单的显示/隐藏控制 */ li ul { display: none; } li:hover ul, li.active-hover ul, li:focus-within ul { display: block; }
3. 无障碍优化细节
别忘了添加ARIA属性,让屏幕阅读器能正确识别菜单状态:
- 给根
<li>添加aria-haspopup="true",表明它有下拉菜单; - 用
aria-expanded属性标识菜单是否展开,展开时设为true,收起时设为false; - 初始状态下给子菜单设置
aria-hidden="true",展开时改为false。
这样一套下来,既能完美模拟hover的视觉效果,又能满足键盘导航的无障碍要求,用户用Tab或方向键都能顺畅操作下拉菜单。
内容的提问来源于stack exchange,提问作者Danigoodw
相关产品推荐
相关产品推荐

