如何找到子导航中的最后一个可聚焦元素以实现Tab键导航跳转?
如何定位子导航的最后一个可聚焦元素,实现Tab键跳转主导航的功能
先明确需求:我们有四个主导航项(Shirts、Pants、Footwear、Headgear),点击每个项会展开对应的子导航菜单;主导航类名是.mainNav,子导航类名是.subNav-0这类(建议统一加.subNav类方便选择)。要求用户用Tab键遍历到子导航最后一个元素时,自动关闭当前子导航,同时把焦点跳到下一个主导航项——比如在Shirts子导航的Z Brand上按Tab,就关闭Shirts子导航并聚焦到Pants。
第一步:明确可聚焦元素范围
默认能被Tab键聚焦的元素包括:
- 带
href属性的<a>标签 <button>、<input>、<select>、<textarea>- 设置了
tabindex≥0的元素
注意:你提供的HTML里<a>标签写法有误,应该写成<a href="#">...</a>,不然不带href的<a>默认是不可聚焦的,除非手动加tabindex。
第二步:获取子导航的最后一个可聚焦元素
针对当前展开的子导航,用选择器筛选出所有可聚焦元素,再取最后一个即可:
// 假设当前展开的子导航元素是subNavEl const focusableElements = subNavEl.querySelectorAll('a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])'); const lastFocusableEl = focusableElements[focusableElements.length - 1];
第三步:监听Tab键事件,实现跳转逻辑
给最后一个可聚焦元素绑定keydown事件,判断用户是否按了Tab键(且没按Shift,Shift+Tab是往回跳,不需要处理),然后执行关闭子导航、聚焦下一个主导航的操作:
lastFocusableEl.addEventListener('keydown', (e) => { if (e.key === 'Tab' && !e.shiftKey) { e.preventDefault(); // 阻止默认的Tab跳转行为 // 关闭当前子导航 subNavEl.style.display = 'none'; // 找到当前主导航项的下一个兄弟元素 const currentMainItem = document.querySelector('.mainNav a:focus'); let nextMainItem = currentMainItem.nextElementSibling; // 如果是最后一个主导航,跳回第一个 if (!nextMainItem) { nextMainItem = document.querySelector('.mainNav a:first-child'); } nextMainItem.focus(); // 聚焦到下一个主导航 } });
完整示例代码
修正后的HTML(修复<a>标签错误,统一子导航类名)
<div class="mainNav"> <a href="#">Shirts</a> <a href="#">Pants</a> <a href="#">Footwear</a> <a href="#">Headgear</a> </div> <div class="subNav subNav-0" style="display: none;"> <h1>Shirts</h1> <a href="#">ABC Brand</a> <a href="#">Guess</a> <a href="#">Levis</a> <a href="#">Z Brand</a> </div> <div class="subNav subNav-1" style="display: none;"> <h1>Pants</h1> <a href="#">ABC Brand</a> <a href="#">Guess</a> <a href="#">Levis</a> <a href="#">Z Brand</a> </div> <div class="subNav subNav-2" style="display: none;"> <h1>Headgear</h1> <a href="#">Big Caps</a> <a href="#">Levis</a> <a href="#">Pacific Hatwear</a> <a href="#">Polo</a> <a href="#">Stetson</a> </div> <div class="subNav subNav-3" style="display: none;"> <h1>Footwear</h1> <a href="#">Adidas</a> <a href="#">Asics</a> <a href="#">Brooks</a> <a href="#">Converse</a> <a href="#">New Balance</a> <a href="#">Nike</a> <a href="#">Puma</a> <a href="#">Saucony</a> </div>
对应的JavaScript
// 绑定主导航点击事件,控制子导航显示隐藏 const mainNavItems = document.querySelectorAll('.mainNav a'); const subNavs = document.querySelectorAll('.subNav'); mainNavItems.forEach((item, index) => { item.addEventListener('click', (e) => { e.preventDefault(); // 先隐藏所有子导航 subNavs.forEach(sub => sub.style.display = 'none'); // 显示当前对应的子导航 const targetSubNav = document.querySelector(`.subNav-${index}`); targetSubNav.style.display = 'block'; // 给当前子导航的最后可聚焦元素绑定Tab事件 setupTabNavigation(targetSubNav, item); }); }); // 封装Tab导航处理逻辑 function setupTabNavigation(subNavEl, currentMainItem) { // 移除之前绑定的事件,避免重复绑定 const prevLastElSelector = subNavEl.dataset.lastFocusEl; if (prevLastElSelector) { const prevLastEl = document.querySelector(prevLastElSelector); if (prevLastEl) { prevLastEl.removeEventListener('keydown', handleTab); } } // 获取子导航内所有可聚焦元素 const focusableElements = subNavEl.querySelectorAll('a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])'); if (focusableElements.length === 0) return; const lastFocusableEl = focusableElements[focusableElements.length - 1]; // 存储元素选择器,方便后续移除事件 subNavEl.dataset.lastFocusEl = lastFocusableEl.tagName === 'A' ? `a[href="${lastFocusableEl.getAttribute('href')}"]` : `#${lastFocusableEl.id}`; // 绑定Tab处理函数 lastFocusableEl.addEventListener('keydown', handleTab); function handleTab(e) { if (e.key === 'Tab' && !e.shiftKey) { e.preventDefault(); // 关闭子导航 subNavEl.style.display = 'none'; // 找到下一个主导航项 let nextMainItem = currentMainItem.nextElementSibling; // 循环到第一个主导航 if (!nextMainItem) { nextMainItem = document.querySelector('.mainNav a:first-child'); } nextMainItem.focus(); } } }
内容的提问来源于stack exchange,提问作者linnse
相关产品推荐
相关产品推荐

