
本文详解如何通过维护当前激活下拉项索引状态,结合事件委托与 stopPropagation,实现多级下拉菜单的「单开互斥」行为——即点击任一菜单项时,自动收起其余已展开的下拉内容。
本文详解如何通过维护当前激活下拉项索引状态,结合事件委托与 `stoppropagation`,实现多级下拉菜单的「单开互斥」行为——即点击任一菜单项时,自动收起其余已展开的下拉内容。
在构建响应式导航栏时,常见的交互需求是:同一时间仅允许一个下拉菜单处于展开状态。原始代码中,每个 .menu 元素独立绑定 click 事件,仅操作自身对应的 .item,导致多个下拉项可同时显示;而全局 window 点击监听逻辑又因闭包变量 i 的终值问题(循环结束后的 i === menu.length)失效,无法正确关闭对应项。
核心解法在于引入状态管理:使用一个全局变量 openIndex 记录当前展开项的索引(初始为 -1 表示无展开项),并在每次点击主菜单时执行「先关闭旧项、再切换新项」的原子操作。
以下是优化后的完整 JavaScript 实现:
const menu = document.getElementsByClassName('menu');
const menucontent = document.getElementsByClassName('item');
const subTitle = document.getElementsByClassName('sub-title');
const subItem = document.getElementsByClassName('sub-item');
let openIndex = -1; // 跟踪当前展开的下拉菜单索引
for (let i = 0; i < menu.length; i++) {
menu[i].addEventListener('click', function(e) {
// 判断是否点击了子菜单标题或下拉图标(避免触发主菜单折叠逻辑)
const isSubmenuToggle = e.target.matches('.sub-title') || e.target.matches('.fas');
if (isSubmenuToggle) {
e.stopPropagation(); // 阻止事件冒泡至 .menu 父级,防止误触发主菜单收起
subItem[i].classList.toggle('present');
subTitle[i].classList.toggle('clear');
return;
}
// 主菜单点击逻辑:关闭之前展开项(若存在且非当前项),再切换当前项
if (openIndex !== -1 && openIndex !== i) {
menucontent[openIndex].classList.remove('show');
menu[openIndex].classList.remove('darkgrey');
}
menucontent[i].classList.toggle('show');
menu[i].classList.toggle('darkgrey');
openIndex = menucontent[i].classList.contains('show') ? i : -1;
});
}
// 全局点击收起:点击菜单区域外时关闭当前展开项
window.addEventListener('click', function(e) {
if (
!e.target.closest('.menu') &&
!e.target.closest('.item') &&
!e.target.closest('.sub-title') &&
!e.target.closest('.fas')
) {
if (openIndex !== -1) {
menucontent[openIndex].classList.remove('show');
menu[openIndex].classList.remove('darkgrey');
openIndex = -1;
}
}
});✅ 关键改进说明:
- 使用
e.target.closest(selector)替代多个matches()判断,语义更清晰、兼容性更好; -
e.stopPropagation()精准拦截子菜单点击向父.menu的冒泡,避免折叠冲突; -
openIndex在每次主菜单点击后实时更新,确保状态一致性; - 全局 window 监听器中增加
closest()安全校验,防止误判嵌套元素点击。
⚠️ 注意事项:
- HTML 结构需严格保持
.menu → .item、.sub-title → .sub-item的层级关系; - CSS 中
.item.show样式必须定义display: flex(或block),否则classList.toggle('show')无效; - 若后续扩展为动态渲染菜单,建议改用事件委托 +
dataset标识,避免重复绑定。
该方案轻量、可靠,无需依赖框架,适用于任何基于原生 DOM 的多级下拉菜单场景。

















