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>
运行效果
- 桌面端:显示水平导航菜单,固定在页面顶部。
- 移动端:显示抽屉式菜单,点击按钮展开,点击遮罩关闭。
通过这个例子,我们实现了一个功能完备、易于扩展的响应式导航菜单,你可以根据需求进一步美化样式或添加其他功能!