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

如何用JavaScript实现移动端侧边菜单多级子菜单的height过渡效果?

解决方案:基于scrollHeight实现多级移动端菜单高度过渡动画

一、调整CSS样式(核心是用height替代display控制可见性)

移除原CSS中通过display/opacity/visibility控制子菜单显示的逻辑,改用height: 0和overflow: hidden实现隐藏,同时保留height的过渡效果:

header{
    position: relative;
}
#main-mobile-menue{
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    background-color: #fff;
    z-index: 100;
    transform: translateX(-100%);
    transition: transform 0.5s;
}
#main-mobile-menue .mobile-header-menue{
    display: flex;
    justify-content: space-between;
    align-items: center;
    box-shadow:  2px 2px 8px 2px #f0eded;
    padding: 20px 15px;
}
#close-menue{
    border: none;
    background-color: transparent;
    font-size: 30px;
    color: #556370;
}
.main-mobile-menue-container{
    padding: 5px 0px;
}
.main-mobile-menue-container .main-mobile-menue-ul {
    padding-left: 0px !important;
    list-style:none;
}
.main-mobile-menue-container .main-mobile-menue-ul .main-m-li a{
    text-decoration: none;
    display: flex;
    justify-content: space-between;
    padding: 11px 24px;
    font-size: 18px;
    color: #556370;
}
.main-mobile-menue-container .main-mobile-menue-ul .main-m-li {
    border-bottom: 1px solid #ddddddd7;
}
/* 修改子菜单默认样式:用height和overflow控制隐藏 */
.main-m-li-sub .sub-m-ul,
.sub-m-li-sub .sub-m-ul{
    list-style: none;
    background-color: #f7f7f7;
    padding-left: 24px !important;
    height: 0;
    overflow: hidden;
    transition: height 0.3s ease-out; 
}
.sub-m-li-link,.main-m-a-sub-sub{
    font-size:16px !important;
}
.sub-m-li-sub .main-m-a-sub-sub{
    justify-content: start !important;
    align-items: center;
}
.sub-m-li-sub .main-m-a-sub-sub .sub-sub-menue-m{
    margin-left: 10px;
}

.open-menue-btn{
    transform: translateX(0%) !important;
}

二、优化JavaScript逻辑(计算scrollHeight实现动态高度过渡)

通过scrollHeight获取菜单的完整高度(包含所有已展开的嵌套子菜单),实现平滑过渡;同时处理过渡完成后的高度自适应,以及嵌套菜单的联动:

// 监听所有子菜单触发按钮的点击事件
document.addEventListener('mouseup', function(e) {
    if (e.target.classList.contains('main-m-a-sub')) {
        const targetMenu = e.target.parentElement.children[1];
        const isExpanded = targetMenu.style.height !== '0px' && targetMenu.style.height !== '';

        if (isExpanded) {
            // 折叠菜单:先设置为当前scrollHeight,再过渡到0
            targetMenu.style.height = targetMenu.scrollHeight + 'px';
            setTimeout(() => {
                targetMenu.style.height = '0px';
            }, 10);
            // 折叠时需要通知父菜单重新计算高度
            updateParentMenuHeight(targetMenu);
        } else {
            // 展开菜单:先设置为0,再过渡到完整scrollHeight
            targetMenu.style.height = '0px';
            setTimeout(() => {
                targetMenu.style.height = targetMenu.scrollHeight + 'px';
            }, 10);
            // 展开时通知父菜单重新计算高度
            updateParentMenuHeight(targetMenu);
        }

        // 过渡结束后设置height为auto,适配内容动态变化
        targetMenu.addEventListener('transitionend', function onTransitionEnd() {
            if (!isExpanded) {
                targetMenu.style.height = 'auto';
            }
            targetMenu.removeEventListener('transitionend', onTransitionEnd);
        });
    }
});

// 更新父菜单的高度(处理嵌套菜单展开/折叠时的联动)
function updateParentMenuHeight(menu) {
    const parentLi = menu.closest('.main-m-li, .sub-m-li');
    if (!parentLi) return;
    const parentMenu = parentLi.parentElement;
    if (parentMenu.classList.contains('sub-m-ul')) {
        const parentBtn = parentLi.querySelector('.main-m-a-sub');
        if (parentBtn) {
            // 重新计算父菜单的高度
            parentMenu.style.height = parentMenu.scrollHeight + 'px';
        }
    }
}

// 侧边栏展开/折叠逻辑保持不变
const closeButtom = document.getElementById('close-menue');
const mobile_nav = document.getElementById('main-mobile-menue');
closeButtom.addEventListener('click', function() {
    mobile_nav.classList.remove('open-menue-btn');
});
const open_menue = document.getElementById('mobile-menue');
open_menue.addEventListener('click', function() {
    mobile_nav.classList.add('open-menue-btn');
});

三、关键逻辑说明

  1. 为什么用scrollHeight:scrollHeight会返回元素内容的完整高度(包括溢出隐藏的部分),完美解决auto高度无法过渡的问题。
  2. 过渡动画的实现:先强制设置元素的高度为当前scrollHeight(折叠时)或0(展开时),再触发过渡,避免CSS无法识别auto到具体数值的变化。
  3. 嵌套菜单联动:当子菜单展开/折叠时,递归更新父菜单的高度,确保父菜单能适配子菜单的高度变化,不会出现内容截断。
  4. 过渡后设置auto:动画结束后将高度设为auto,保证如果菜单内容动态变化(比如异步加载),高度能自动适配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 01:22:13