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

移动端导航交互优化求助:单一级菜单互斥展开与过渡控制

嘿,我来帮你搞定这两个问题——菜单的交互逻辑优化,还有从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语法优化点

  1. 变量声明:用const(值不变的变量)和let(值需要修改的变量)替代var,彻底避免变量提升和全局污染问题。
  2. 箭头函数:简化回调函数写法,比如事件监听的async (e) => {...},还能自动保留当前上下文(类方法除外)。
  3. 数组操作:用Array.from()或展开语法[...nodeList]把DOM集合(NodeList)转换成数组,方便使用map、filter等数组方法。
  4. 异步处理:用async/await替代jQuery的$.when()或回调嵌套,让异步逻辑更清晰可读。
  5. 解构赋值:如果需要同时获取多个DOM元素,可以用解构简化代码:
    const [menuItem1, menuItem2] = document.querySelectorAll('.nm__item--lvl1');
    

性能改进方向

  1. 减少DOM查询:把常用的DOM元素(比如菜单容器)缓存到变量里,不要每次事件触发都重新查询DOM,DOM操作是JS中最耗时的操作之一。
  2. 避免不必要的重绘重排:确保CSS过渡只作用在height、transform这类性能友好的属性上(transform性能更优,要是能改用transform: scaleY()实现展开效果,流畅度会更好)。
  3. 代码模块化:把菜单逻辑封装成类,便于维护和复用,比如:
    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');
    
  4. 防抖/节流(按需):如果菜单有hover这类高频触发的事件,可以用防抖函数避免频繁执行逻辑,你的场景是click暂时不需要,但可以作为扩展知识储备。

当前脚本性能评估(假设你之前用jQuery)

原生ES6脚本的性能肯定比jQuery好,因为jQuery要兼容旧浏览器,有很多封装层。如果之前的jQuery代码是给每个菜单项绑事件,改成事件委托后,内存占用会大幅降低;另外,原生的classList.replace()比jQuery的toggleClass()或removeClass()+addClass()更高效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:23:59