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

news2024/11/20 20:08:15

在现代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.coloradmin.cn/o/2244232.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

哥德巴赫猜想渐行渐远

我现在的工作&#xff0c;表明经典分析可能出了问题&#xff0c;如此则连Vinogradov的三素数定理都不成立了&#xff0c;更别说基于L-函数方程的陈氏定理“12”了。事实上即使L-函数方程成立&#xff0c;由于我指出Siegel定理不成立&#xff0c;陈景润和张益唐的工作就不成立。…

【支持向量机(SVM)】:相关概念及API使用

文章目录 1 SVM相关概念1.1 SVM引入1.1.1 SVM思想1.1.2 SVM分类1.1.3 线性可分、线性和非线性的区分 1.2 SVM概念1.3 支持向量概念1.4 软间隔和硬间隔1.5 惩罚系数C1.6 核函数 2 SVM API使用2.1 LinearSVC API 说明2.2 鸢尾花数据集案例2.3 惩罚参数C的影响 1 SVM相关概念 1.1…

GraphRAG+Ollama实现本地部署+neo4j可视化结果

GraphRAGOllama实现本地部署neo4j可视化结果 前言一、GraphRAGOllama本地部署补充说明 二、neo4j可视化GraphRAG1.windows安装neo4j2.启动neo4j服务3.进入neo4j的webui界面4.使用neo4J可视化GraphRAG索引5.neo4j不删除旧数据&#xff0c;新建一个数据库 总结 前言 最近部署微软…

ssm142视频点播系统设计与实现+vue(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;视频点播系统设计与实现 摘 要 互联网发展到如今也近20年之久&#xff0c;视频信息一直作为互联网发展中的一个重要角色在不断更新进化。视频信息从最初的文本显示到现在集文字、视频、音频与一体&#xff0c;成为一…

Python全方位技术教程

Python全方位技术教程 引言 Python是一种强大且易于学习的编程语言&#xff0c;因其简洁的语法和丰富的库而受到广泛欢迎。无论是数据分析、机器学习、Web开发&#xff0c;还是自动化脚本&#xff0c;Python都能胜任。本文将深入探讨Python的各个方面&#xff0c;帮助读者全面…

父组件提交时让各自的子组件验证表格是否填写完整

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 父组件中有三个表格&#xff0c;表格中时输入框&#xff0c;有些输入框是必填的&#xff0c;在父组件提交时需要验证这三个表格的必填输入框中是否有没填写的。 原因分析&#xff1a; 提示&#xff1a…

基于SpringBoot+RabbitMQ完成应⽤通信

前言&#xff1a; 经过上面俩章学习&#xff0c;我们已经知道Rabbit的使用方式RabbitMQ 七种工作模式介绍_rabbitmq 工作模式-CSDN博客 RabbitMQ的工作队列在Spring Boot中实现&#xff08;详解常⽤的⼯作模式&#xff09;-CSDN博客作为⼀个消息队列,RabbitMQ也可以⽤作应⽤程…

从0-1训练自己的数据集实现火焰检测

随着工业、建筑、交通等领域的快速发展,火灾作为一种常见的灾难性事件,对生命财产安全造成了严重威胁。为了提高火灾的预警能力,减少火灾损失,火焰检测技术应运而生,成为火灾监控和预防的有效手段之一。 传统的火灾检测方法,如烟雾探测器、温度传感器等,存在响应时间慢…

计算机网络 (3)计算机网络的性能

一、计算机网络性能指标 速率&#xff1a; 速率是计算机网络中最重要的性能指标之一&#xff0c;它指的是数据的传送速率&#xff0c;也称为数据率&#xff08;Data Rate&#xff09;或比特率&#xff08;Bit Rate&#xff09;。速率的单位是比特/秒&#xff08;bit/s&#xff…

豆包MarsCode

#豆包MarsCode上新workspace# 1. 首先&#xff0c;个人所写的代码&#xff0c;会提交到gitee或者阿里的云效仓库&#xff0c;但是想在数据仓库导入的时候&#xff0c;只有github的仓库&#xff0c;希望可以加入国内的数据仓库 2. 加载不流畅&#xff0c;在使用网页版的时候&…

物联网智能技术的深入探讨与案例分析

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

C语言零基础入门

一、输入输出 &#xff08;1&#xff09;scanf scanf 是C语言中的一个标准库函数&#xff0c;用于从标准输入&#xff08;通常是键盘&#xff09;读取数据。scanf 函数定义在 <stdio.h> 头文件中。 #include <stdio.h>int main(void) {//读取整数 int num;print…

Jmeter数据库压测之达梦数据库的配置方法

目录 1、概述 2、测试环境 3、数据库压测配置 3.1 安装jmeter 3.2 选择语言 3.3 新建测试计划 3.4 配置JDBC连接池 3.5 配置线程组 3.6 配置测试报告 3.7 执行测试 1、概述 Jmeter是Apache组织开发的基于Java的压力测试工具&#xff0c;用于对软件做压力测试。 它最…

golang开发一个海盗王的登录更新器

前段时间&#xff0c;用golang配合界面库govcl开发一个海盗王的登陆更新器&#xff0c;实现多区注册和文件更新分离不同服务器等新功能。 由于govcl没有更换皮肤的功能&#xff0c;界面都是默认&#xff0c;不好看。 找了很多go语言的gui库&#xff0c;都没有符合要求的。 后来…

好用的js组件库

lodash https://www.lodashjs.com/https://www.lodashjs.com/ uuid 用于生成随机数&#xff0c;常用于生成id标识 GitHub - uuidjs/uuid: Generate RFC-compliant UUIDs in JavaScripthttps://github.com/uuidjs/uuid dayjs 常用于时间的处理 安装 | Day.js中文网 (fenxi…

ElasticSearch学习篇17_《检索技术核心20讲》最邻近检索-局部敏感哈希、乘积量化PQ思路

目录 场景在搜索引擎和推荐引擎中&#xff0c;对相似文章去重是一个非常重要的环节&#xff0c;另外是拍照识花、摇一摇搜歌等场景都可以使用它快速检索。 基于敏感性哈希的检索更擅长处理字面上的相似而不是语义上的相似。 向量空间模型ANN检索加速思路 局部敏感哈希编码 随…

针对git、giteeVSCode连接的使用 || Live Share插件使用

1.下载git 链接 打开终端&#xff0c;桌面鼠标右键 2.配置密钥 登录gitee。 设置密钥 查看官方文档 跟着教程 复制最后的输出进行密钥添加 验证是否添加成功 3.创建&连接远程仓库 创建仓库 git终端进行配置 远程仓库克隆到本地 桌面终端clone,克隆他人|自己的仓库到本地…

【Pikachu】XML外部实体注入实战

若天下不定&#xff0c;吾往&#xff1b;若世道不平&#xff0c;不回&#xff01; 1.XXE漏洞实战 首先写入一个合法的xml文档 <?xml version "1.0"?> <!DOCTYPE gfzq [<!ENTITY gfzq "gfzq"> ]> <name>&gfzq;</name&…

【vmware+ubuntu16.04】ROS学习_博物馆仿真克隆ROS-Academy-for-Beginners软件包处理依赖报错问题

首先安装git 进入终端&#xff0c;输入sudo apt-get install git 安装后&#xff0c;创建一个工作空间名为tutorial_ws&#xff0c; 输入 mkdir tutorial_ws#创建工作空间 cd tutorial_ws#进入 mkdir src cd src git clone https://github.com/DroidAITech/ROS-Academy-for-Be…

【澜舟科技-注册/登录安全分析报告】

前言 由于网站注册入口容易被机器执行自动化程序攻击&#xff0c;存在如下风险&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露&#xff0c;不符合国家等级保护的要求。短信盗刷带来的拒绝服务风险 &#xff0c;造成用户无法登陆、注册&#xff0c;大量收到垃圾短信的…