移动端导航交互优化求助:单一级菜单互斥展开与过渡控制
嘿,我来帮你搞定这两个问题——菜单的交互逻辑优化,还有从jQuery转到ES6的脚本改进!
一、菜单交互:先关后开,等待过渡完成
你说的思路完全正确:需要先把所有带nm__lvl--expanded类的<ul>换成nm__lvl--collapsed,而且必须等关闭的过渡动画结束后,再展开当前点击的菜单。这里用原生ES6实现的话,关键是用transitionend事件配合async/await来控制时序,避免动画冲突。
实现代码示例
// 缓存菜单容器,用事件委托提升性能 const menuContainer = document.querySelector('.nm'); menuContainer.addEventListener('click', async (e) => { // 找到点击的一级菜单项(确保点击的是菜单项或其内部元素) const targetItem = e.target.closest('.nm__item--lvl1'); if (!targetItem) return; const targetMenu = targetItem.querySelector('.nm__lvl'); const isCurrentlyExpanded = targetMenu.classList.contains('nm__lvl--expanded'); // 如果当前菜单已经展开,直接关闭即可 if (isCurrentlyExpanded) { targetMenu.classList.replace('nm__lvl--expanded', 'nm__lvl--collapsed'); return; } // 获取所有已展开的一级菜单 const expandedMenus = document.querySelectorAll('.nm__lvl--expanded'); // 有展开的菜单时,先等待所有关闭动画完成 if (expandedMenus.length > 0) { await Promise.all(Array.from(expandedMenus).map(menu => { return new Promise(resolve => { // 替换类名触发关闭动画 menu.classList.replace('nm__lvl--expanded', 'nm__lvl--collapsed'); // 监听过渡结束事件,只触发一次后移除监听 const onTransitionEnd = (e) => { // 只监听我们关心的过渡属性(比如height,避免其他属性触发多次) if (e.propertyName !== 'height') return; menu.removeEventListener('transitionend', onTransitionEnd); resolve(); }; menu.addEventListener('transitionend', onTransitionEnd); }); })); } // 所有关闭动画完成后,展开当前菜单 targetMenu.classList.replace('nm__lvl--collapsed', 'nm__lvl--expanded'); });
关键细节说明
- 事件委托:不给每个一级菜单项单独绑点击事件,而是把监听绑定到父容器,减少内存占用,新增菜单项也能自动生效。
- 等待过渡完成:用
Promise封装每个菜单的关闭过程,配合Promise.all等待所有关闭动画结束,再执行展开逻辑,避免动画重叠。 - 避免重复触发:监听
transitionend后立即移除事件,防止内存泄漏,同时判断过渡属性,避免同一元素多个过渡属性触发多次回调。
二、从jQuery转到ES6:脚本优化与性能改进
从jQuery转到原生ES6,核心是利用ES6的语法糖简化代码,同时遵循原生JS的性能最佳实践。
ES6语法优化点
- 变量声明:用
const(值不变的变量)和let(值需要修改的变量)替代var,彻底避免变量提升和全局污染问题。 - 箭头函数:简化回调函数写法,比如事件监听的
async (e) => {...},还能自动保留当前上下文(类方法除外)。 - 数组操作:用
Array.from()或展开语法[...nodeList]把DOM集合(NodeList)转换成数组,方便使用map、filter等数组方法。 - 异步处理:用
async/await替代jQuery的$.when()或回调嵌套,让异步逻辑更清晰可读。 - 解构赋值:如果需要同时获取多个DOM元素,可以用解构简化代码:
const [menuItem1, menuItem2] = document.querySelectorAll('.nm__item--lvl1');
性能改进方向
- 减少DOM查询:把常用的DOM元素(比如菜单容器)缓存到变量里,不要每次事件触发都重新查询DOM,DOM操作是JS中最耗时的操作之一。
- 避免不必要的重绘重排:确保CSS过渡只作用在
height、transform这类性能友好的属性上(transform性能更优,要是能改用transform: scaleY()实现展开效果,流畅度会更好)。 - 代码模块化:把菜单逻辑封装成类,便于维护和复用,比如:
class DropdownMenu { constructor(selector) { this.container = document.querySelector(selector); this.init(); } init() { this.container.addEventListener('click', this.handleClick.bind(this)); } async handleClick(e) { // 点击逻辑 } async closeAll() { // 关闭所有展开菜单的逻辑 } openMenu(menu) { // 展开单个菜单的逻辑 } } // 使用时只需实例化 new DropdownMenu('.nm'); - 防抖/节流(按需):如果菜单有hover这类高频触发的事件,可以用防抖函数避免频繁执行逻辑,你的场景是click暂时不需要,但可以作为扩展知识储备。
当前脚本性能评估(假设你之前用jQuery)
原生ES6脚本的性能肯定比jQuery好,因为jQuery要兼容旧浏览器,有很多封装层。如果之前的jQuery代码是给每个菜单项绑事件,改成事件委托后,内存占用会大幅降低;另外,原生的classList.replace()比jQuery的toggleClass()或removeClass()+addClass()更高效。
内容的提问来源于stack exchange,提问作者QJan84
相关产品推荐
相关产品推荐

