如何修复基于ul的导航菜单展开无动画、直接跳转的问题?
修复基于ul的导航菜单展开动画不生效问题
导航菜单展开时无过渡动画直接跳转,核心问题及修复方案如下:
问题根源
- 样式优先级冲突:CSS中
#main-nav:not([expanded]) .menus已设置height:0,但JS中先手动设置height:initial测量高度,再设height:0,紧接着设置expanded属性,导致CSS的height:0被覆盖,浏览器无法识别动画初始状态。 - DOM渲染合并:浏览器会批量处理样式变更,设置
height:0后立刻用requestAnimationFrame设目标高度,两次变更被合并,未触发过渡动画。 - 事件监听参数错误:
transitionend事件监听使用了捕获阶段(第三个参数true),无必要且可能导致事件触发异常。
修复方案
- 调整
expanded属性设置时机:先设置expanded属性,再处理高度动画,避免CSS样式干扰JS的手动高度控制。 - 强制DOM重排:在设置
height:0后,读取menu.offsetHeight触发浏览器重排,确保初始状态被确认后再设置目标高度。 - 修正事件监听参数:移除捕获阶段参数,使用默认冒泡阶段监听
transitionend事件。 - 优化关闭动画:关闭菜单时先将高度设为当前实际高度,再触发过渡到0,保证关闭过程也有动画效果。
修复后的完整代码
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> nav ul { list-style: none; margin: 0; padding: 0; } #main-nav { width: 80vw; background-color: #0000000A; display: flex; padding: 10px 0; margin: 20px auto; } #main-nav .menus { flex: 1; overflow: hidden; margin-left: max(0px, (100vw - 300px) / 4); padding-left: 20px; transition: height 1s ease; height: 0; } #main-nav[expanded] .menus { height: auto; } #main-nav .menus li a { text-decoration: none; font-family: "Crimson Text", serif; letter-spacing: 2px; padding: 5px 5px 10px 0; margin: 5px 0; display: block; } #main-nav .menus > li > a { color: #7c6949; } #main-nav .menus > li ul { padding-left: 20px; } #main-nav .menus > li ul li:not(:last-child) a { border-bottom: 1px solid #0000000A; } #main-nav .control { background: none; border: none; display: flex; margin-right: 10px; background: orange; padding: 10px; cursor: pointer; } #main-nav[expanded] .control.open { display: none; } #main-nav:not([expanded]) .control.close { display: none; } </style> </head> <body> <nav id="main-nav"> <ul class="menus"> <li> <a href="#">About</a> <ul> <li><a href="/">Biographies</a></li> <li><a href="/">Testimonials</a></li> <li><a href="/">FAQ</a></li> </ul> </li> <li> <a href="#">Subscribe</a> <ul> <li><a href="/">Subscribe</a></li> <li><a href="/">Renew</a></li> </ul> </li> <li> <a href="#">Contact</a> <ul> <li><a href="/">Contact Us</a></li> </ul> </li> </ul> <div class="controls"> <button class="control open" onclick="openNavMenu()"> open </button> <button class="control close" onclick="closeNavMenu()"> close </button> </div> </nav> <script> const expanded = "expanded" function transitionComplete(event) { const target = event.target; if (target.style.height !== 'auto') { target.style.height = 'auto'; } target.removeEventListener('transitionend', transitionComplete); } function openNavMenu() { const nav = document.querySelector("#main-nav"); const menus = nav.querySelectorAll(".menus"); nav.setAttribute(expanded, "true"); menus.forEach(menu => { // 先设置为auto获取实际高度 menu.style.height = 'auto'; const expandedHeight = menu.offsetHeight; // 重置为0 menu.style.height = '0px'; // 强制重排 menu.offsetHeight; // 设置目标高度触发动画 menu.style.height = `${expandedHeight}px`; // 监听过渡结束,恢复auto高度 menu.addEventListener('transitionend', transitionComplete); }); } function closeNavMenu() { const nav = document.querySelector("#main-nav"); const menus = nav.querySelectorAll(".menus"); menus.forEach(menu => { // 获取当前高度 const currentHeight = menu.offsetHeight; // 先设置为当前高度,避免直接跳变 menu.style.height = `${currentHeight}px`; // 强制重排 menu.offsetHeight; // 设置为0触发动画 menu.style.height = '0px'; // 监听过渡结束,移除expanded属性 menu.addEventListener('transitionend', () => { nav.removeAttribute(expanded); menu.removeEventListener('transitionend', arguments.callee); }); }); } </script> </body> </html>
内容的提问来源于stack exchange,提问作者SSMSJM
相关产品推荐
相关产品推荐

