e.stopPropagation()引发菜单切换异常及关闭按钮失效问题
问题修复方案
核心问题拆解
- 切换行打开菜单时原菜单未关闭:
e.stopPropagation()阻止事件冒泡到document,无法触发原菜单的关闭逻辑,还会重复添加监听导致状态混乱。 - 移除
e.stopPropagation()后菜单无法打开:打开菜单时同步添加document监听,事件冒泡会立即触发该监听,直接关闭菜单。 - Close按钮无效:点击Close按钮时仍走Open的打开逻辑,未区分按钮的当前状态。
修复后的代码
JavaScript
const menu = document.getElementById("menu"); const table = document.getElementById("table"); const option_buttons = Array.from(table.querySelectorAll(".btn_option")); let target_row = null; table.addEventListener("click", (e) => { const btn = e.target.closest(".btn_option"); if (!option_buttons.includes(btn)) return; const row = btn.closest(".row"); // 点击Close按钮时关闭菜单 if (row.classList.contains("selected")) { close_menu(); return; } // 已有菜单打开时先关闭 if (menu.is_open) { close_menu(); } // 定位并打开新菜单 menu.style.top = btn.getBoundingClientRect().top + "px"; menu.style.display = "flex"; menu.is_open = true; target_row = row; target_row.classList.add("selected"); // 延迟添加监听,避免当前事件冒泡立即触发关闭 setTimeout(() => { document.documentElement.addEventListener("click", handle_click_outside_menu); }, 0); }); function handle_click_outside_menu(e) { const btn = e.target.closest(".btn_option"); // 点击Open/Close按钮时不触发关闭 if (btn && option_buttons.includes(btn)) return; // 点击菜单内部不触发关闭 if (e.target === menu || menu.contains(e.target)) return; close_menu(); document.documentElement.removeEventListener("click", handle_click_outside_menu); } function close_menu() { menu.style.display = "none"; menu.is_open = false; if (target_row) { target_row.classList.remove("selected"); target_row = null; } }
CSS(保留原代码)
.table { overflow: hidden; } .row { display: flex; gap: 2rem; padding: 0.5rem 0; border-bottom: 0.0625rem solid #ccc; } .row .btn_option > span { pointer-events: none; display: block; } .row:not(.selected) .btn_option > span:last-child { display: none; } .row:is(.selected) .btn_option > span:first-child { display: none; } .menu { display: none; position: absolute; left: 10rem; padding: 0.5rem; background: #ccc; flex-direction: column; gap: 0.5rem; } .menu::before { content: ''; position: absolute; top: 0; left: -0.75rem; width: 0; height: 0; border-top: 0.75rem solid transparent; border-bottom: 0.75rem solid transparent; border-right: 0.75rem solid #ccc; }
HTML(保留原代码)
<div class="table" id="table"> <div class="row"> <span>Item 1</span> <button class="btn_option" type="button"><span>Open</span><span>Close</span></button> </div> <div class="row"> <span>Item 2</span> <button class="btn_option" type="button"><span>Open</span><span>Close</span></button> </div> <div class="row"> <span>Item 3</span> <button class="btn_option" type="button"><span>Open</span><span>Close</span></button> </div> <div class="row"> <span>Item 4</span> <button class="btn_option" type="button"><span>Open</span><span>Close</span></button> </div> <div class="row"> <span>Item 5</span> <button class="btn_option" type="button"><span>Open</span><span>Close</span></button> </div> </div> <div class="menu" id="menu"> <button type="button">View</button> <button type="button">Edit</button> <button type="button">Delete</button> </div>
关键修改点
- 移除
e.stopPropagation(),改用事件目标判断避免阻止冒泡导致的逻辑断层。 - 点击按钮时先检查行的选中状态,区分Open/Close操作。
- 打开新菜单前强制关闭已打开的菜单,避免状态冲突。
- 用
setTimeout延迟添加document监听,防止当前事件循环立即触发关闭。 - 在关闭逻辑中增加空值判断,避免报错。
内容的提问来源于stack exchange,提问作者Oscar
相关产品推荐
相关产品推荐

