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的特性导致:
- 当鼠标快速从下方菜单项移到上方菜单项时,前一个菜单项的子容器正在执行
opacity从1到0的过渡动画,此时容器虽然透明,但仍然存在于页面中,会响应鼠标事件。 - 当鼠标向右移动时,很容易进入这个透明容器的区域,而子容器是父菜单项的子元素,鼠标悬停在子容器上会重新激活父菜单项的
:hover状态,导致子容器再次展开。 - 向下切换菜单时,鼠标向右移动的路径不会经过前一个子容器的区域,因此不会触发该问题。
另外,原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; }
方案说明
pointer-events: none:当子容器处于透明状态时,禁用它的鼠标事件,这样鼠标移到透明区域也不会触发父菜单项的:hover。- 优化过渡时序:通过
transition: visibility 0s 300ms让visibility在opacity和max-height过渡结束后再隐藏,保证动画过程流畅,同时隐藏后元素彻底不响应事件。 - 修复语法错误:补充分号,避免布局异常。
经过这些修改后,快速切换菜单再向右移动鼠标时,前一个容器就不会意外展开了,完美解决你的问题。
内容的提问来源于stack exchange,提问作者vtQQ
相关产品推荐
相关产品推荐

