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

Vue3中document.querySelector类切换后仍选中元素的问题求助

Vue3 实现侧边栏菜单开合(替代jQuery SlideUp/SlideDown)的问题

我想用Vue3实现类似jQuery的slideUp()/slideDown()的侧边栏菜单开合功能,但遇到了问题:多次点击后菜单刚打开就立即关闭,仿佛元素状态没有正确更新。

当前代码实现

Script 部分

export default {
  mounted() {
       let hasSubmenu = Array.from(document.querySelectorAll(".hasSubmenu.close"));
       let self = this;
       hasSubmenu.forEach(function (el) {
            el.addEventListener("click", self.menuOpen.bind(this, el));
        });
  },
  methods:{
          menuOpen(el){
              let ul = el.querySelector("ul");
              ul.style.height = `${ul.scrollHeight}px`;
              el.classList.remove("close");
              el.classList.add("open");
              this.menuClose();
          },
          menuClose(){
              document.querySelector(".hasSubmenu.open").addEventListener("click", function (e) {
                  e.currentTarget.querySelector("ul").style.height = "0px";
                  e.currentTarget.classList.remove("open");
                  e.currentTarget.classList.add("close");
              });
          }
  },
}

Template 部分

<ul id="main-nav">
  <li class="hasSubmenu close">
    <a href="#" class="menuItem"><div> Main Menu One</div></a>
    <ul style="height: 0px">
      <li>
        <a href="#" class="py-3"> Sub Menu One</a>
       </li>
       <li>
        <a href="#" class="py-3"> Sub Menu Three</a>
       </li>
     </ul>
   </li>
</ul>

问题原因

  1. 重复绑定事件:每次调用menuOpen时,都会执行menuClose给当前元素绑定新的点击事件。多次点击后,元素上会积累多个点击回调,后续点击会同时触发展开和收起逻辑,导致菜单刚打开就关闭。
  2. 脱离Vue响应式:直接操作原生DOM和类名,没有利用Vue的数据驱动特性,状态管理混乱,容易出现DOM和数据不一致的问题。

解决方案

方案1:修复原生DOM逻辑(不推荐,不符合Vue思想)

避免重复绑定事件,改为单次绑定并判断元素状态:

export default {
  mounted() {
    document.querySelectorAll(".hasSubmenu").forEach(el => {
      el.addEventListener("click", this.toggleMenu);
    });
  },
  methods: {
    toggleMenu(e) {
      const el = e.currentTarget;
      const ul = el.querySelector("ul");
      
      if (el.classList.contains("close")) {
        // 展开菜单
        ul.style.height = `${ul.scrollHeight}px`;
        el.classList.replace("close", "open");
      } else {
        // 收起菜单
        ul.style.height = "0px";
        el.classList.replace("open", "close");
      }
    }
  }
}

方案2:Vue响应式实现(推荐)

利用Vue的数据驱动和<Transition>组件实现平滑过渡,完全抛弃原生DOM操作:

完整单文件组件代码

<script setup>
import { ref, onMounted } from 'vue';

// 管理菜单展开状态
const isMenuOpen = ref(false);
// 引用子菜单DOM元素
const subMenu = ref(null);
// 存储子菜单高度,用于过渡动画
const menuHeight = ref(0);

onMounted(() => {
  if (subMenu.value) {
    menuHeight.value = subMenu.value.scrollHeight;
    // 设置CSS变量供过渡动画使用
    document.documentElement.style.setProperty('--menu-height', `${menuHeight.value}px`);
  }
});

// 切换菜单开合状态
const toggleMenu = () => {
  isMenuOpen.value = !isMenuOpen.value;
};
</script>

<template>
<ul id="main-nav">
  <li class="hasSubmenu" @click="toggleMenu">
    <a href="#" class="menuItem"><div> Main Menu One</div></a>
    <Transition name="slide">
      <ul ref="subMenu" v-show="isMenuOpen">
        <li>
          <a href="#" class="py-3"> Sub Menu One</a>
         </li>
         <li>
          <a href="#" class="py-3"> Sub Menu Three</a>
         </li>
       </ul>
    </Transition>
  </li>
</ul>
</template>

<style scoped>
/* 实现slide过渡动画 */
.slide-enter-from,
.slide-leave-to {
  height: 0;
  overflow: hidden;
}

.slide-enter-active,
.slide-leave-active {
  transition: height 0.3s ease;
}

.slide-enter-to,
.slide-leave-from {
  height: var(--menu-height);
}
</style>

这种方式完全遵循Vue的响应式思想,状态管理清晰,不会出现事件重复绑定的问题,同时通过<Transition>组件实现了平滑的滑动效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 12:31:25