如何用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'); });
三、关键逻辑说明
- 为什么用scrollHeight:
scrollHeight会返回元素内容的完整高度(包括溢出隐藏的部分),完美解决auto高度无法过渡的问题。 - 过渡动画的实现:先强制设置元素的高度为当前scrollHeight(折叠时)或0(展开时),再触发过渡,避免CSS无法识别auto到具体数值的变化。
- 嵌套菜单联动:当子菜单展开/折叠时,递归更新父菜单的高度,确保父菜单能适配子菜单的高度变化,不会出现内容截断。
- 过渡后设置auto:动画结束后将高度设为auto,保证如果菜单内容动态变化(比如异步加载),高度能自动适配。
内容的提问来源于stack exchange,提问作者Fardad Moridi
相关产品推荐
相关产品推荐

