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

如何修复基于ul的导航菜单展开无动画、直接跳转的问题?

修复基于ul的导航菜单展开动画不生效问题

导航菜单展开时无过渡动画直接跳转,核心问题及修复方案如下:

问题根源

  1. 样式优先级冲突:CSS中#main-nav:not([expanded]) .menus已设置height:0,但JS中先手动设置height:initial测量高度,再设height:0,紧接着设置expanded属性,导致CSS的height:0被覆盖,浏览器无法识别动画初始状态。
  2. DOM渲染合并:浏览器会批量处理样式变更,设置height:0后立刻用requestAnimationFrame设目标高度,两次变更被合并,未触发过渡动画。
  3. 事件监听参数错误:transitionend事件监听使用了捕获阶段(第三个参数true),无必要且可能导致事件触发异常。

修复方案

  1. 调整expanded属性设置时机:先设置expanded属性,再处理高度动画,避免CSS样式干扰JS的手动高度控制。
  2. 强制DOM重排:在设置height:0后,读取menu.offsetHeight触发浏览器重排,确保初始状态被确认后再设置目标高度。
  3. 修正事件监听参数:移除捕获阶段参数,使用默认冒泡阶段监听transitionend事件。
  4. 优化关闭动画:关闭菜单时先将高度设为当前实际高度,再触发过渡到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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 22:30:49