在前端开发中,经常需要实现动态菜单的展开和折叠功能。本文将探讨三种在 Vue.js 中实现这一功能的方法,并深入分析其原理,从原生 JavaScript 的实现逐步过渡到 Vue.js 中的应用。
1. 原生 JavaScript 实现
在原生 JavaScript 中,我们可以通过操作 DOM 元素的样式来实现菜单的展开和折叠。以下是一个简单的示例:
document.addEventListener('DOMContentLoaded', () => {const menuItems = document.querySelectorAll('.menu-item');menuItems.forEach(item => {const submenu = item.querySelector('.submenu');if (submenu) {item.addEventListener('click', () => {submenu.style.display = submenu.style.display === 'none' ? 'block' : 'none';});}});
});
这段代码的核心是遍历所有菜单项,为包含子菜单的项添加点击事件监听器。点击事件触发时,切换子菜单的 display
样式属性,从而实现展开和折叠效果。
2. Vue.js 中的数组索引法
在 Vue.js 中,我们可以利用数据驱动的特性来更优雅地实现菜单的展开和折叠。一种常见的方法是使用数组索引来跟踪当前展开的菜单项。
<template><ul class="menu-list"><li v-for="(item, index) in menuItems" :key="index" class="menu-item"><div @click="toggleItem(index)">{{ item.title }} <span v-if="item.submenu">({{ openIndexes.includes(index) ? '收起' : '展开' }})</span></div><ul v-if="item.submenu && openIndexes.includes(index)" class="submenu"><li v-for="subItem in item.submenu" :key="subItem">{{ subItem }}</li></ul></li></ul>
</template><script>
export default {data() {return {openIndexes: [],/* ... 菜单数据 ... */menuItems: [{ title: "🏠 首页" },{ title: "📱 科技社", submenu: ["互联网漫谈", "科技动态"] },{ title: "🎁 福利社", submenu: ["优惠活动", "每日抽奖"] },{ title: "🧰 资源社", submenu: ["学习资料", "工具下载"] },{ title: "💬 有话说" },{ title: "👭 友情频道" },{ title: "🍺 关于小站", submenu: ["站点介绍", "联系我们"] },],};},methods: {toggleItem(index) {if (this.openIndexes.includes(index)) {this.openIndexes = this.openIndexes.filter(i => i !== index);} else {this.openIndexes.push(index);}},},
};
</script>
openIndexes
数组存储了所有展开的菜单项的索引。toggleItem
方法根据点击的索引来添加或移除 openIndexes
中的索引,从而控制子菜单的显示。
3. Vue.js 中的动态属性法
另一种更简洁的方法是动态为每个有子菜单的项添加 isOpen
属性。
<template><ul class="menu-list"><li v-for="(item, index) in menuItems" :key="index" class="menu-item"><div @click="toggleItem(item)"> <!-- 直接传递 item 对象 -->{{ item.title }} <span v-if="item.submenu">({{ item.isOpen ? '收起' : '展开' }})</span></div><ul v-if="item.submenu && item.isOpen" class="submenu"><li v-for="subItem in item.submenu" :key="subItem">{{ subItem }}</li></ul></li></ul>
</template><script>
export default {data() {return {/* ... 菜单数据 ... */menuItems: [{ title: "🏠 首页" },{ title: "📱 科技社", submenu: ["互联网漫谈", "科技动态"] },{ title: "🎁 福利社", submenu: ["优惠活动", "每日抽奖"] },{ title: "🧰 资源社", submenu: ["学习资料", "工具下载"] },{ title: "💬 有话说" },{ title: "👭 友情频道" },{ title: "🍺 关于小站", submenu: ["站点介绍", "联系我们"] },],};},mounted() {this.menuItems.forEach(item => {if (item.submenu) {item.isOpen = false;}});},methods: {toggleItem(item) {if (item.submenu) {item.isOpen = !item.isOpen;}},},
};
</script>
在 mounted
生命周期钩子中,我们为每个具有子菜单的项添加 isOpen
属性,并初始化为 false
。toggleItem
方法直接修改 item.isOpen
的值来控制展开和折叠。
总结
三种方法各有优劣。原生 JavaScript 方法较为直接,但操作 DOM 较多。数组索引法利用了 Vue.js 的数据驱动特性,但需要维护一个额外的数组。动态属性法最为简洁优雅,直接操作数据对象,符合 Vue.js 的响应式 principles. 选择哪种方法取决于项目的具体需求和代码风格。
完整代码示例 (动态属性法):
<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">{{ item.isOpen ? '▲' : '▼' }} <!-- 使用 item.isOpen 控制箭头 --></span></div><ul v-if="item.submenu && item.isOpen" class="submenu"> <!-- 使用 item.isOpen 控制显示 --><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,menuItems: [{ title: "🏠 首页" },{ title: "📱 科技社", submenu: ["互联网漫谈", "科技动态"] },{ title: "🎁 福利社", submenu: ["优惠活动", "每日抽奖"] },{ title: "🧰 资源社", submenu: ["学习资料", "工具下载"] },{ title: "💬 有话说" },{ title: "👭 友情频道" },{ title: "🍺 关于小站", submenu: ["站点介绍", "联系我们"] },],};},mounted() { // 在组件挂载后初始化 isOpenthis.menuItems.forEach(item => {if (item.submenu) {item.isOpen = false; // 为有子菜单的项添加 isOpen 属性,初始值为 false}});},methods: {toggleDrawer() {this.isDrawerOpen = !this.isDrawerOpen;},toggleItem(index) {const item = this.menuItems[index];if (item.submenu) {item.isOpen = !item.isOpen;}},},
};
</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>
希望本文能帮助你理解在 Vue.js 中实现动态菜单展开折叠的不同方法,并根据你的实际情况选择最合适的方案。