You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

e.stopPropagation()引发菜单切换异常及关闭按钮失效问题

问题修复方案

核心问题拆解

  1. 切换行打开菜单时原菜单未关闭:e.stopPropagation()阻止事件冒泡到document,无法触发原菜单的关闭逻辑,还会重复添加监听导致状态混乱。
  2. 移除e.stopPropagation()后菜单无法打开:打开菜单时同步添加document监听,事件冒泡会立即触发该监听,直接关闭菜单。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 04:47:05