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

如何用JavaScript实现鼠标悬停时保持子菜单展开

解决悬停显示子菜单且点击子菜单不消失的问题

首先咱们分析下问题根源:当你把鼠标从父元素.item移到子菜单.submenu时,如果两者之间存在间隙,或者父元素的hover状态没覆盖到子菜单区域,就会导致父元素hover失效,子菜单跟着消失。另外你原有的JavaScript点击事件,也可能在点击子菜单时意外触发关闭逻辑。

下面给你两种可行的解决方案:

方案一:纯CSS实现(推荐,简洁高效)

利用CSS的hover伪类特性——当鼠标停留在子元素上时,父元素的hover状态会持续保持,这样就能保证子菜单不会消失。同时调整子菜单的定位,确保和父元素无缝衔接,避免鼠标移动时出现间隙。

修改后的CSS代码

/* 默认隐藏子菜单 */
.submenu {
  display: none;
  position: absolute;
  left: 0;
  top: 100%; /* 子菜单顶部紧贴父元素底部,消除间隙 */
  min-width: 200px;
  margin: 0; /* 移除默认margin,避免产生间隙 */
  padding: 0; /* 可选:移除默认内边距,统一布局 */
}

/* 父元素hover时显示子菜单 */
.item:hover .submenu {
  display: block;
}

/* 给父元素设置相对定位,让子菜单的绝对定位基于它 */
.item {
  position: relative;
}

为什么这能解决问题?

  • 当鼠标悬停在.item上,子菜单显示;移到子菜单上时,因为子菜单是.item的子元素,.item的hover状态会一直保持,子菜单自然不会消失。
  • position: relative给子菜单的绝对定位提供了正确的参考容器,确保子菜单位置精准。
  • 移除默认边距+设置top:100%,彻底避免父元素和子菜单之间出现间隙,防止鼠标移动时意外跳出父元素的hover范围。

方案二:结合JavaScript优化(适配移动端点击需求)

如果你需要同时支持移动端的点击触发(移动端没有hover效果),可以保留JS逻辑,但要修改事件监听,避免点击子菜单时误关父菜单。

修改后的JavaScript代码

const items = document.querySelectorAll(".item");
const menu = document.querySelector(".menu");

// 切换子菜单显示状态
function toggleItem(e) {
  // 阻止事件冒泡,防止点击子菜单时触发父元素的事件
  e.stopPropagation();
  
  // 只有点击父元素的触发区域(而非子菜单)时,才切换状态
  if (!e.target.closest(".submenu")) {
    if (this.classList.contains("submenu-active")) {
      this.classList.remove("submenu-active");
    } else {
      // 先关闭其他已打开的子菜单
      menu.querySelector(".submenu-active")?.classList.remove("submenu-active");
      this.classList.add("submenu-active");
    }
  }
}

// 鼠标进入父元素时打开子菜单
function handleMouseEnter() {
  // 关闭其他打开的子菜单
  menu.querySelector(".submenu-active")?.classList.remove("submenu-active");
  this.classList.add("submenu-active");
}

// 鼠标离开时延迟关闭,给用户移到子菜单的缓冲时间
function handleMouseLeave() {
  setTimeout(() => {
    // 只有当鼠标完全离开父元素和子菜单时,才关闭
    if (!this.matches(":hover") && !this.querySelector(".submenu:hover")) {
      this.classList.remove("submenu-active");
    }
  }, 100);
}

// 给带子菜单的父元素绑定事件
for (let item of items) {
  if (item.querySelector(".submenu")) {
    item.addEventListener("click", toggleItem, false);
    item.addEventListener("mouseenter", handleMouseEnter, false);
    item.addEventListener("mouseleave", handleMouseLeave, false);
    // 给父元素的触发链接添加focus事件,支持键盘导航
    item.querySelector("a[tabindex='0']").addEventListener("focus", () => {
      handleMouseEnter.call(item);
    });
  }
}

对应的CSS调整

.submenu {
  display: none;
  position: absolute;
  left: 0;
  top: 100%;
  min-width: 200px;
  margin: 0;
}

/* 通过类控制子菜单显示,兼容JS逻辑 */
.item.submenu-active .submenu {
  display: block;
}

.item {
  position: relative;
}

关键优化点

  • 用e.stopPropagation()阻止点击子菜单时事件冒泡到父元素,避免误触发关闭逻辑。
  • 鼠标离开时添加100ms延迟,给用户足够时间把鼠标移到子菜单上,防止子菜单突然消失。
  • 离开前检查鼠标位置,只有完全脱离父元素和子菜单区域时才关闭子菜单。

额外注意事项

  • 一定要给.item设置position: relative,否则子菜单的绝对定位会基于整个文档,位置会错乱。
  • 可以移除子菜单默认的padding和margin,让布局更规整,减少间隙风险。
  • 如果需要支持键盘导航,记得给子菜单的链接设置合适的tabindex,保证页面可访问性。

内容的提问来源于stack exchange,提问作者Arthur Fortin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 18:38:11