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

Vue 3 + Tailwind移动端导航展开无动画问题及组件优化咨询

响应式导航菜单动画修复与组件重构方案

一、动画问题修复

问题原因

当前使用两个状态变量(mobileMenuOpen和linksVisible)控制移动端菜单显示,逻辑混乱导致Vue Transition无法正确捕获进入状态的变化;同时存在冗余的空按钮干扰状态判断。

修复步骤

  1. 合并控制变量
    将两个状态变量合并为单一变量,简化状态逻辑:

    <script setup lang="ts">
    import { onBeforeUnmount, onMounted, ref } from "vue";
    
    const isMobileMenuOpen = ref(false);
    
    function toggleMobileMenu(): void {
      isMobileMenuOpen.value = !isMobileMenuOpen.value;
    }
    
    function closeLinksOnResize() {
      if (window.innerWidth > 768) {
        isMobileMenuOpen.value = false;
      }
    }
    
    onMounted(() => {
      window.addEventListener("resize", closeLinksOnResize);
    });
    
    onBeforeUnmount(() => {
      window.removeEventListener("resize", closeLinksOnResize);
    });
    </script>
    
  2. 调整模板逻辑

    • 移除无内容的冗余按钮
    • 用单一变量控制移动端菜单显示,同时绑定checkbox状态实现双向同步:
    <template>
      <div class="flex justify-center">
        <header
          class="p-5 shadow-md bg-white rounded-full mt-6 md:flex md:items-center md:justify-between"
        >
          <!-- 桌面端导航 -->
          <ul class="hidden md:flex">
            <li class="mx-4 my-6 md:my-0">
              <a
                href="#"
                class="text-xl hover:text-cyan-500 duration-500 focus:text-cyan-500"
                >HOME</a
              >
            </li>
            <li class="mx-4 my-6 md:my-0">
              <a
                href="#"
                class="text-xl hover:text-cyan-500 duration-500 focus:text-cyan-500"
                >SERVICE</a
              >
            </li>
            <li class="mx-4 my-6 md:my-0">
              <a
                href="#"
                class="text-xl hover:text-cyan-500 duration-500 focus:text-cyan-500"
                >ABOUT</a
              >
            </li>
            <li class="mx-4 my-6 md:my-0">
              <a
                href="#"
                class="text-xl hover:text-cyan-500 duration-500 focus:text-cyan-500"
                >CONTACT</a
              >
            </li>
            <li class="mx-4 my-6 md:my-0">
              <a
                href="#"
                class="text-xl hover:text-cyan-500 duration-500 focus:text-cyan-500"
                >BLOG'S</a
              >
            </li>
          </ul>
    
          <!-- 移动端菜单按钮 -->
          <input
            v-model="isMobileMenuOpen"
            type="checkbox"
            role="button"
            aria-label="Display the menu"
            class="menu md:hidden mt-1"
          />
          
          <transition name="fade-slide">
            <ul
              v-if="isMobileMenuOpen"
              class="rounded-lg md:flex md:items-center z-10 md:z-auto md:static absolute bg-white w-full left-0 md:w-auto py-2 pl-9 md:opacity-100 md:rounded-none md:mt-0 mt-8"
            >
              <li class="mx-4 my-6 md:my-0">
                <a
                  href="#"
                  class="text-xl focus:text-cyan-500 hover:text-cyan-500 duration-500"
                  >HOME</a
                >
              </li>
              <li class="mx-4 my-6 md:my-0">
                <a
                  href="#"
                  class="text-xl focus:text-cyan-500 hover:text-cyan-500 duration-500"
                  >SERVICE</a
                >
              </li>
              <li class="mx-4 my-6 md:my-0">
                <a
                  href="#"
                  class="text-xl focus:text-cyan-500 hover:text-cyan-500 duration-500"
                  >ABOUT</a
                >
              </li>
              <li class="mx-4 my-6 md:my-0">
                <a
                  href="#"
                  class="text-xl focus:text-cyan-500 hover:text-cyan-500 duration-500"
                  >CONTACT</a
                >
              </li>
              <li class="mx-4 my-6 md:my-0">
                <a
                  href="#"
                  class="text-xl focus:text-cyan-500 hover:text-cyan-500 duration-500"
                  >BLOG'S</a
                >
              </li>
            </ul>
          </transition>
        </header>
      </div>
    </template>
    
  3. 验证动画效果
    现在点击菜单按钮,进入动画会正常触发:菜单从上方(translateY(-50px))淡入,关闭时执行反向动画,与预设样式一致。

二、组件重构建议

1. 提取导航链接数据,消除重复代码

桌面端和移动端导航链接完全重复,将链接数据提取为数组,用v-for统一渲染:

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from "vue";

const isMobileMenuOpen = ref(false);
// 导航链接数据
const navLinks = ref([
  { text: "HOME", href: "#" },
  { text: "SERVICE", href: "#" },
  { text: "ABOUT", href: "#" },
  { text: "CONTACT", href: "#" },
  { text: "BLOG'S", href: "#" },
]);

function toggleMobileMenu(): void {
  isMobileMenuOpen.value = !isMobileMenuOpen.value;
}

function closeLinksOnResize() {
  if (window.innerWidth > 768) {
    isMobileMenuOpen.value = false;
  }
}

onMounted(() => {
  window.addEventListener("resize", closeLinksOnResize);
});

onBeforeUnmount(() => {
  window.removeEventListener("resize", closeLinksOnResize);
});
</script>

模板中替换两个导航列表:

<!-- 桌面端导航 -->
<ul class="hidden md:flex">
  <li v-for="link in navLinks" :key="link.text" class="mx-4 my-6 md:my-0">
    <a
      :href="link.href"
      class="text-xl hover:text-cyan-500 duration-500 focus:text-cyan-500"
    >
      {{ link.text }}
    </a>
  </li>
</ul>

<!-- 移动端导航 -->
<transition name="fade-slide">
  <ul
    v-if="isMobileMenuOpen"
    class="rounded-lg md:flex md:items-center z-10 md:z-auto md:static absolute bg-white w-full left-0 md:w-auto py-2 pl-9 md:opacity-100 md:rounded-none md:mt-0 mt-8"
  >
    <li v-for="link in navLinks" :key="link.text" class="mx-4 my-6 md:my-0">
      <a
        :href="link.href"
        class="text-xl focus:text-cyan-500 hover:text-cyan-500 duration-500"
      >
        {{ link.text }}
      </a>
    </li>
  </ul>
</transition>

2. 语义化优化

用<nav>标签包裹导航列表,提升页面语义化:

<header class="p-5 shadow-md bg-white rounded-full mt-6 md:flex md:items-center md:justify-between">
  <nav>
    <!-- 桌面端导航列表 -->
  </nav>
  
  <!-- 移动端按钮和导航列表 -->
</header>

3. 简化响应式监听(可选)

如果不想手动监听resize事件,可使用VueUse的useWindowSize工具简化代码:

import { useWindowSize, watch } from "@vueuse/core";

const { width } = useWindowSize();

watch(width, (newWidth) => {
  if (newWidth > 768) {
    isMobileMenuOpen.value = false;
  }
});

4. 样式优化

将移动端菜单的pl-9改为更通用的px-6保持视觉一致性;同时调整动画缓动函数,让过渡更自然:

.fade-slide-enter-active,
.fade-slide-leave-active {
  transition: transform 0.3s ease-out, opacity 0.3s ease-out;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 03:57:04