当前位置: 首页 > article >正文

Vue实现响应式导航菜单:桌面端导航栏 + 移动端抽屉式菜单

在现代Web开发中,为了提升用户体验,响应式设计已成为必备技能。本文将通过Vue.js,创建一个响应式导航菜单,桌面端显示顶部导航栏,移动端则切换为抽屉式菜单,并具备点击遮罩关闭的功能。以下是具体实现步骤。


项目需求分析

我们希望实现以下功能: 1. 桌面端宽屏(大于 768px):顶部导航菜单,水平布局,导航栏固定在页面顶部。 2. 移动端窄屏(小于或等于 768px):隐藏顶部导航栏,显示抽屉式菜单,点击遮罩层或关闭按钮可关闭抽屉。 3. 响应式设计:根据屏幕宽度自动切换布局,实时监听窗口大小变化。


实现步骤

1. 创建 Vue 项目并初始化布局

首先,初始化 Vue 项目。以下是 HTML 模板的基础结构,包含桌面端和移动端的两种菜单形式:

<template>
  <div id="app">
    <!-- 顶部导航栏(仅桌面端显示) -->
    <header>
      <nav class="top-nav" v-if="!isMobile">
        <ul class="menu-list">
          <li v-for="(item, index) in menuItems" :key="index">
            <a href="#">{{ item.title }}</a>
          </li>
        </ul>
      </nav>

      <!-- 菜单按钮(仅移动端显示) -->
      <button v-if="isMobile" class="menu-button" @click="toggleDrawer">☰</button>
    </header>

    <!-- 抽屉式菜单 -->
    <div class="drawer" :class="{ open: isDrawerOpen }">
      <div class="drawer-header">
        <span class="logo">导航菜单</span>
        <button class="close-button" @click="toggleDrawer">×</button>
      </div>
      <ul class="menu-list">
        <li v-for="(item, index) in menuItems" :key="index">
          <a href="#">{{ item.title }}</a>
        </li>
      </ul>
    </div>

    <!-- 抽屉遮罩层 -->
    <div v-if="isMobile && isDrawerOpen" class="overlay" @click="closeDrawer"></div>
  </div>
</template>

如果有二级菜单需要去做一些优化修改,使用css鼠标hover显示子级菜单或者通过js控制子级菜单展开折叠,以下是一个vue模版:

<template>
  <div id="app">
    <!-- 菜单按钮 -->
    <button class="menu-button" @click="toggleDrawer">☰</button>

    <!-- 抽屉菜单 -->
    <div class="drawer" :class="{ open: isDrawerOpen }">
      <div class="drawer-header">
        <span class="logo">Blog</span>
        <button class="close-button" @click="toggleDrawer">×</button>
      </div>
      <ul class="menu-list">
        <li v-for="(item, index) in menuItems" :key="index" class="menu-item">
          <div class="menu-title" @click="toggleItem(index)">
            <span>{{ item.title }}</span>
            <span v-if="item.submenu" class="arrow">
              {{ openIndex === index ? "▲" : "▼" }}
            </span>
          </div>
          <ul v-if="item.submenu && openIndex === index" class="submenu">
            <li v-for="(subItem, subIndex) in item.submenu" :key="subIndex">
              {{ subItem }}
            </li>
          </ul>
        </li>
      </ul>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      isDrawerOpen: false, // 抽屉是否打开
      openIndex: null, // 当前展开的菜单索引
      menuItems: [
        { title: "🏠 首页" },
        { title: "📱 科技社", submenu: ["互联网漫谈", "科技动态"] },
        { title: "🎁 福利社", submenu: ["优惠活动", "每日抽奖"] },
        { title: "🧰 资源社", submenu: ["学习资料", "工具下载"] },
        { title: "💬 有话说" },
        { title: "👭 友情频道" },
        { title: "🍺 关于小站", submenu: ["站点介绍", "联系我们"] },
      ],
    };
  },
  methods: {
    toggleDrawer() {
      this.isDrawerOpen = !this.isDrawerOpen; // 切换抽屉状态
    },
    toggleItem(index) {
      // 展开或折叠二级菜单
      this.openIndex = this.openIndex === index ? null : index;
    },
  },
};
</script>
<style scoped>
body {
  margin: 0;
  font-family: Arial, sans-serif;
}

.menu-button {
  position: fixed;
  top: 16px;
  left: 16px;
  font-size: 24px;
  background: none;
  border: none;
  cursor: pointer;
}

.drawer {
  position: fixed;
  top: 0;
  left: 0;
  width: 75%;
  height: 100%;
  background-color: white;
  transform: translateX(-100%);
  transition: transform 0.3s ease;
  box-shadow: 2px 0 8px rgba(0, 0, 0, 0.2);
}

.drawer.open {
  transform: translateX(0);
}

.drawer-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px;
  background-color: #f5f5f5;
  border-bottom: 1px solid #ddd;
}

.logo {
  font-size: 20px;
  font-weight: bold;
}

.close-button {
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
}

.menu-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.menu-item {
  padding: 16px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;
  border-bottom: 1px solid #ddd;
}

.menu-title {
  width: 100%;
}

.submenu {
  width: 100%;
  list-style: none;
  padding: 0 16px;
  margin: 0;
  padding: 0 16px;
  background-color: #f9f9f9;
}

.submenu li {
  padding: 8px 0;
  border-bottom: 1px solid #eee;
}

.arrow {
  font-size: 12px;
}
</style>

2. 使用 Vue 管理状态

在脚本部分,我们通过 data 定义了抽屉状态 isDrawerOpen 和当前是否是移动端的标志 isMobile,并通过 methods 处理逻辑,包括菜单开关和窗口大小监听。

<script>
export default {
  data() {
    return {
      isDrawerOpen: false, // 抽屉是否打开
      isMobile: false,     // 当前是否是移动端
      menuItems: [
        { title: "🏠 首页" },
        { title: "📱 科技社" },
        { title: "🎁 福利社" },
        { title: "🧰 资源社" },
        { title: "💬 有话说" },
        { title: "👭 友情频道" },
        { title: "🍺 关于小站" },
      ],
    };
  },
  methods: {
    toggleDrawer() {
      this.isDrawerOpen = !this.isDrawerOpen; // 切换抽屉开关
    },
    closeDrawer() {
      this.isDrawerOpen = false; // 关闭抽屉
    },
    checkScreenSize() {
      this.isMobile = window.innerWidth <= 768; // 判断是否为移动端
    },
  },
  mounted() {
    this.checkScreenSize(); // 初次加载时检查屏幕尺寸
    window.addEventListener("resize", this.checkScreenSize); // 监听窗口大小变化
  },
  beforeDestroy() {
    window.removeEventListener("resize", this.checkScreenSize); // 组件销毁时移除监听器
  },
};
</script>

3. 添加样式

为实现桌面端顶部导航栏和移动端抽屉菜单,我们需要分别设置两种样式。

<style scoped>
/* 通用样式 */
body {
  margin: 0;
  font-family: Arial, sans-serif;
}

/* 顶部导航栏样式(桌面端) */
.top-nav {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  background-color: #f5f5f5;
  padding: 16px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  z-index: 1000;
}

.top-nav .menu-list {
  display: flex;
  justify-content: flex-start;
  list-style: none;
  margin: 0;
  padding: 0;
}

.top-nav .menu-list li {
  margin-right: 20px;
}

.top-nav .menu-list li a {
  text-decoration: none;
  color: #333;
}

/* 移动端抽屉菜单样式 */
.menu-button {
  font-size: 24px;
  background: none;
  border: none;
  cursor: pointer;
}

.drawer {
  position: fixed;
  top: 0;
  left: 0;
  width: 75%;
  height: 100%;
  background-color: white;
  transform: translateX(-100%);
  transition: transform 0.3s ease;
  box-shadow: 2px 0 8px rgba(0, 0, 0, 0.2);
  z-index: 1000;
}

.drawer.open {
  transform: translateX(0);
}

.drawer-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px;
  background-color: #f5f5f5;
  border-bottom: 1px solid #ddd;
}

.menu-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.menu-list li {
  padding: 16px;
  border-bottom: 1px solid #ddd;
}

.menu-list li a {
  text-decoration: none;
  color: #333;
}

/* 遮罩层 */
.overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  z-index: 999;
}

/* 响应式样式 */
@media (max-width: 768px) {
  .top-nav {
    display: none; /* 隐藏桌面端导航 */
  }
}
</style>

4. 完整代码

整合模板、脚本和样式,完整代码如下:

<template>
  <div id="app">
    <header>
      <nav class="top-nav" v-if="!isMobile">
        <ul class="menu-list">
          <li v-for="(item, index) in menuItems" :key="index">
            <a href="#">{{ item.title }}</a>
          </li>
        </ul>
      </nav>
      <button v-if="isMobile" class="menu-button" @click="toggleDrawer">☰</button>
    </header>

    <div class="drawer" :class="{ open: isDrawerOpen }">
      <div class="drawer-header">
        <span class="logo">导航菜单</span>
        <button class="close-button" @click="toggleDrawer">×</button>
      </div>
      <ul class="menu-list">
        <li v-for="(item, index) in menuItems" :key="index">
          <a href="#">{{ item.title }}</a>
        </li>
      </ul>
    </div>

    <div v-if="isMobile && isDrawerOpen" class="overlay" @click="closeDrawer"></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isDrawerOpen: false,
      isMobile: false,
      menuItems: [
        { title: "🏠 首页" },
        { title: "科技社" },
        { title: "🎁 福利社" },
        { title: "🧰 资源社" },
        { title: "💬 北城有话说" },
        { title: "✈️ TG订阅频道" },
        { title: "🍺 关于小站" },
      ],
    };
  },
  methods: {
    toggleDrawer() {
      this.isDrawerOpen = !this.isDrawerOpen;
    },
    closeDrawer() {
      this.isDrawerOpen = false;
    },
    checkScreenSize() {
      this.isMobile = window.innerWidth <= 768;
    },
  },
  mounted() {
    this.checkScreenSize();
    window.addEventListener("resize", this.checkScreenSize);
  },
  beforeDestroy() {
    window.removeEventListener("resize", this.checkScreenSize);
  },
};
</script>

<style scoped>
/* 样式同上 */
</style>

运行效果

  1. 桌面端:显示水平导航菜单,固定在页面顶部。
  2. 移动端:显示抽屉式菜单,点击按钮展开,点击遮罩关闭。

通过这个例子,我们实现了一个功能完备、易于扩展的响应式导航菜单,你可以根据需求进一步美化样式或添加其他功能!


http://www.kler.cn/a/402144.html

相关文章:

  • 使用 Vue 和 Create-Vue 构建工程化前端项目
  • mysql日志写满出现The table ‘xxxx_amazon_order’ is full
  • 5.4.2-1 编写Java程序在HDFS上创建文件
  • 汽车与摩托车分类数据集
  • 富格林:安全指正规防欺诈套路
  • 【EasyExcel】复杂导出操作-自定义颜色样式等(版本3.1.x)
  • HarmonyOS NEXT应用元服务开发Intents Kit(意图框架服务)习惯推荐方案概述
  • 批量将当前目录里的所有pdf 转化为png 格式
  • 鸿蒙实战:使用显式Want启动Ability
  • 【C++课程学习】:继承:默认成员函数
  • DBSCAN聚类——基于密度的聚类算法(常用的聚类算法)
  • HarmonyOS4+NEXT星河版入门与项目实战-------- Text 组件与国际化实现
  • 魔乐社区平台下载书生模型
  • DNS协议详解:原理、查询过程及常见问题
  • How to install rust in Ubuntu 24.04
  • NAT网络地址转换——Easy IP
  • git操作总结
  • 在Unity中实现电梯升降功能的完整指南
  • 关于selenium元素找不到的问题(Unable to locate element: {“method“:“xpath“,“selector“:“)
  • 使用GDB或Delve对已经运行起来的Go程序进行远程调试
  • 11.13机器学习_KNN和模型选择调优
  • 基于docker搭建mysql主从架构
  • 网络安全协议
  • git在创建分支时如何将默认分支名字设为master
  • Python 使用Django进行单元测试unittest
  • 活着就好20241120