最直接的方式是用 display 控制二级菜单显隐:悬停时设为 block(或 flex),移出时设为 none;需配合 position: relative/absolute 定位,用 mouseenter/mouseleave 避免边界误触发,响应式需兼顾 click 逻辑与 hover 媒体查询。

用 display 控制二级菜单显隐是最直接的方式
鼠标悬停时显示二级菜单,本质就是切换 display 值:从 none 变成 block(或 flex),移出时再切回去。不用 visibility: hidden,因为它占位,会导致布局错乱;也不建议用 opacity + pointer-events,兼容性和交互反馈不如 display 直观。
关键点在于定位和触发时机:
- 一级菜单项(
<li>)需设position: relative,作为二级菜单的定位上下文 - 二级菜单(
<ul></ul>)设position: absolute,并用top/left微调位置,比如left: 100%实现右侧弹出 - 初始状态必须写死
display: none在 CSS 里,不能只靠 JS 控制,否则无样式的页面会闪出菜单
onmouseover 和 onmouseout 的边界问题要小心
用原生事件绑定二级菜单时,onmouseout 容易在一级菜单和二级菜单之间移动时误触发,导致菜单一闪即逝。这不是 bug,是事件冒泡和元素间隙造成的正常行为。
更稳的做法是改用 mouseenter/mouseleave —— 它们不冒泡,且只在真正进出父容器时触发:
const menuItems = document.querySelectorAll('#menu > li');
menuItems.forEach(item => {
item.addEventListener('mouseenter', () => {
item.querySelector('ul')?.style.display = 'block';
});
item.addEventListener('mouseleave', () => {
item.querySelector('ul')?.style.display = 'none';
});
});
注意:如果二级菜单有子项(三级),得额外处理层级 hover 延迟或加防抖,否则快速扫过时仍可能断连。
用 hidden 属性替代 display 时要注意兼容性
HTML5 的 hidden 属性语义清晰,写法也简洁:<ul hidden></ul>。但它等价于 display: none,不是 visibility: hidden,这点常被误解。
不过它有几个现实限制:
- IE 不支持(含 IE11),若需兼容旧环境,必须回退到
style.display - 不能和
display同时存在——如果 CSS 里写了ul { display: block },hidden会被覆盖,失效 - JS 中判断是否隐藏,得用
el.hasAttribute('hidden'),而不是读el.style.display
点击展开菜单比悬停更适合触屏设备
悬停(hover)在手机和平板上基本无效,因为没有“悬停”这个状态。如果你的菜单需要响应式支持,得准备两套逻辑:
- PC 端:用
mouseenter/mouseleave配合display - 移动端:监听
click,并在一级菜单项上加一个 toggle 类(如is-open),用 CSS 控制.is-open ul { display: block } - 检测方式别用 UA 字符串,改用
matchMedia('(hover: hover)')更可靠
最麻烦的其实是过渡动画——display 无法做 CSS 过渡,真要加淡入/滑动效果,得先设 opacity: 0; max-height: 0; overflow: hidden,再用 transition 动 opacity 和 max-height,但 max-height 需预估高度,不然会卡顿。











