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

CSS垂直悬停菜单快速鼠标移动触发动画异常问题

问题:纯CSS垂直菜单悬停展开容器的异常行为

问题描述

我用纯CSS实现了一个垂直菜单,鼠标悬停菜单项时会展开对应的子列表容器,正常使用时一切正常,但遇到了一个奇怪的bug:

  • 当快速将鼠标从下方菜单项(比如Cats)移到上方菜单项(比如Dogs),接着向右移动鼠标时,之前的Cats容器会意外重新展开。
  • 这个问题仅在向上切换菜单时触发,向下切换则不会;在Firefox浏览器中需要极快的鼠标移动速度才会出现。
  • 如果在目标菜单项上停留半秒再操作,就能正常打开对应容器,我推测这个问题和容器内的fade动画未完成有关。

相关代码如下:

HTML

<header class="header"> 
  <nav> 
    <ul class="nav flex-column"> 
      <li class="level1 has_children">
        <a href="#">Dogs</a> 
        <ul class="nav extend row"> 
          <li class="level2 col-4 fade-in"><a href="#">Dog Food</a></li> 
          <li class="level2 col-4 fade-in"><a href="#">Dog Clothing</a></li> 
          <li class="level2 col-4 fade-in"><a href="#">Dog Toys</a></li> 
        </ul> 
      </li> 
      <li class="level1 has_children">
        <a href="#">Cats</a> 
        <ul class="nav extend row"> 
          <li class="level2 col-4 fade-in"><a href="#">Cat Food</a></li> 
          <li class="level2 col-4 fade-in"><a href="#">Cat Clothing</a></li> 
          <li class="level2 col-4 fade-in"><a href="#">Cat Toys</a></li> 
        </ul> 
      </li> 
      <li class="level1 has_children">
        <a href="#">Birds</a> 
        <ul class="nav extend row"> 
          <li class="level2 col-4 fade-in"><a href="#">Bird Food</a></li> 
          <li class="level2 col-4 fade-in"><a href="#">Bird Cages</a></li> 
          <li class="level2 col-4 fade-in"><a href="#">Bird Toys</a></li> 
        </ul> 
      </li> 
    </ul> 
  </nav> 
</header>

CSS

.header { 
  border-top: 2px solid #FF3F5E; 
  float: left; 
  width: 100%; 
} 
.nav { 
  margin: 0; 
  padding: 0; 
  list-style: none; 
  text-align: center; 
  font-size: 1.3rem; 
  width:11%; 
} 
nav > ul { 
  background: #363636; 
} 
li:hover { 
  background: #1d1d1d; 
} 
li a { 
  text-decoration: none; 
  display: block; 
  color: #fff; 
  padding: 11px; 
  text-decoration: none; 
} 
.level1 { 
  width: auto; 
  float: left; 
  text-transform: uppercase; 
} 
.level2 { 
  text-transform: none; 
  transition: 300ms; 
} 
.has_children { } 
.has_children ul { 
  visibility: hidden; 
  max-height: 0; 
  height: auto; 
  position: absolute; 
  left: 211px; 
  opacity: 0; 
  width: 100%; 
  background: inherit; 
  list-style: none; 
  background: #1d1d1d; 
  height:240px; 
  width:400px; 
} 
.has_children a { 
  display: block; 
} 
.has_children a:hover { 
  background: #101010; 
} 
.has_children > a:after { 
  display: inline-block; 
  margin-left: 22px; 
  content: ""; 
  vertical-align: middle; 
  border-top: 0.5rem solid #FF3F5E; 
  border-left: 0.5rem solid transparent; 
  border-right: 0.5rem solid transparent; 
} 
.has_children:hover ul { 
  visibility: visible; 
  opacity: 1; 
  max-height: 500px; 
  transition: 100ms; 
} 
@-webkit-keyframes fadeInMenuItem { 
  from { opacity:0; } 
  to { opacity:1; } 
} 
@-moz-keyframes fadeInMenuItem { 
  from { opacity:0; } 
  to { opacity:1; } 
} 
@keyframes fadeInMenuItem { 
  from { opacity:0; } 
  to { opacity:1; } 
} 
.has_children:hover ul li.fade-in { 
  opacity:0; 
} 
.has_children:hover ul li.fade-in { 
  -webkit-animation:fadeInMenuItem ease-in 1; 
  -moz-animation:fadeInMenuItem ease-in 1; 
  animation:fadeInMenuItem ease-in 1; 
  -webkit-animation-fill-mode:forwards; 
  -moz-animation-fill-mode:forwards; 
  animation-fill-mode:forwards; 
  -webkit-animation-duration:400ms; 
  -moz-animation-duration:400ms; 
  animation-duration:400ms; 
} 
.has_children:hover ul li:nth-child(1) { 
  animation-delay:50ms; 
} 
.has_children:hover ul li:nth-child(2) { 
  animation-delay:100ms; 
} 
.has_children:hover ul li:nth-child(3) { 
  animation-delay:150ms; 
} 
.has_children:hover ul li:nth-child(4) { 
  animation-delay:200ms; 
} 
.has_children:hover ul li { 
  visibility: visible; 
} 
.extend { 
  width:49vw; 
  left:353px; 
  position: absolute; 
  top:0; 
  height:335px; 
} 
li:hover > ul.extend { 
  width:520px 
  height: 230px; 
  position:absolute; 
  left: 211px; 
  transition: 100ms; 
}

原因分析

这个问题的核心在于透明元素仍会接收鼠标事件,结合CSS hover的特性导致:

  1. 当鼠标快速从下方菜单项移到上方菜单项时,前一个菜单项的子容器正在执行opacity从1到0的过渡动画,此时容器虽然透明,但仍然存在于页面中,会响应鼠标事件。
  2. 当鼠标向右移动时,很容易进入这个透明容器的区域,而子容器是父菜单项的子元素,鼠标悬停在子容器上会重新激活父菜单项的:hover状态,导致子容器再次展开。
  3. 向下切换菜单时,鼠标向右移动的路径不会经过前一个子容器的区域,因此不会触发该问题。

另外,原CSS中li:hover > ul.extend的样式存在语法错误(width:520px后缺少分号),可能导致布局异常,加重问题。

解决方案

针对这个问题,我们可以通过禁用透明容器的鼠标事件和优化过渡时序来修复:

修改后的关键CSS代码

.has_children ul {
  visibility: hidden;
  max-height: 0;
  height: auto;
  position: absolute;
  left: 211px;
  opacity: 0;
  width: 100%;
  background: inherit;
  list-style: none;
  background: #1d1d1d;
  height:240px;
  width:400px;
  /* 添加:禁用透明容器的鼠标事件 */
  pointer-events: none;
  /* 优化过渡:让visibility在opacity过渡结束后再隐藏 */
  transition: opacity 300ms, max-height 300ms, visibility 0s 300ms;
}

.has_children:hover ul {
  visibility: visible;
  opacity: 1;
  max-height: 500px;
  /* 优化过渡:显示时立即生效visibility */
  transition: opacity 100ms, max-height 100ms, visibility 0s;
  /* 添加:显示时恢复鼠标事件 */
  pointer-events: auto;
}

/* 修复语法错误 */
li:hover > ul.extend {
  width:520px;
  height: 230px;
  position:absolute;
  left: 211px;
  transition: 100ms;
}

方案说明

  1. pointer-events: none:当子容器处于透明状态时,禁用它的鼠标事件,这样鼠标移到透明区域也不会触发父菜单项的:hover。
  2. 优化过渡时序:通过transition: visibility 0s 300ms让visibility在opacity和max-height过渡结束后再隐藏,保证动画过程流畅,同时隐藏后元素彻底不响应事件。
  3. 修复语法错误:补充分号,避免布局异常。

经过这些修改后,快速切换菜单再向右移动鼠标时,前一个容器就不会意外展开了,完美解决你的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:06:27