Vue实现屏幕缩小时菜单项自动移入「更多」子菜单方案咨询
Vue 自适应横向菜单(溢出项自动移入「更多」子菜单)
核心实现思路
通过监听菜单容器的尺寸变化,实时计算每个菜单项的位置与容器边界的关系,动态将溢出项移入「更多」子菜单;当容器宽度增大时,先把所有项放回主菜单重新计算,自动将能容纳的项保留在主菜单,溢出项再次移入子菜单,实现双向自适应。
具体代码实现
1. 数据结构与初始状态
<script setup> import { ref, onMounted, onUnmounted } from 'vue' // 所有菜单项原始数据 const allMenuItems = ref([ { id: 1, label: '首页' }, { id: 2, label: '产品中心' }, { id: 3, label: '解决方案' }, { id: 4, label: '客户案例' }, { id: 5, label: '关于我们' }, { id: 6, label: '联系我们' }, { id: 7, label: '帮助中心' } ]) // 主菜单显示的项 const mainMenuItems = ref([...allMenuItems.value]) // 更多子菜单的项 const moreMenuItems = ref([]) // 控制更多菜单展开/收起 const isMoreOpen = ref(false) // 菜单容器元素引用 const menuContainer = ref(null) // 更多按钮元素引用 const moreBtn = ref(null) // 防抖函数,避免频繁触发计算 function debounce(func, delay = 200) { let timeoutId return (...args) => { clearTimeout(timeoutId) timeoutId = setTimeout(() => func.apply(this, args), delay) } } </script>
2. 核心计算逻辑
<script setup> // ... 上述代码继续 function adjustMenuItems() { if (!menuContainer.value || !moreBtn.value) return const containerWidth = menuContainer.value.offsetWidth const moreBtnWidth = moreBtn.value.offsetWidth // 主菜单可用宽度 = 容器总宽度 - 更多按钮宽度 const availableWidth = containerWidth - moreBtnWidth // 每次计算前先重置:把所有项放回主菜单,清空子菜单 mainMenuItems.value = [...allMenuItems.value] moreMenuItems.value = [] let totalWidth = 0 const items = menuContainer.value.querySelectorAll('.menu-item:not(.more-btn)') // 遍历计算每个菜单项的宽度总和 for (let i = 0; i < items.length; i++) { totalWidth += items[i].offsetWidth // 如果总和超过可用宽度,把当前及之后的项移到更多菜单 if (totalWidth > availableWidth) { const movedItems = mainMenuItems.value.splice(i) moreMenuItems.value.push(...movedItems) break } } } const debouncedAdjust = debounce(adjustMenuItems) onMounted(() => { // 初始加载时计算一次 adjustMenuItems() // 监听窗口大小变化 window.addEventListener('resize', debouncedAdjust) // 监听菜单容器自身尺寸变化(比如父容器宽度调整) const resizeObserver = new ResizeObserver(debouncedAdjust) resizeObserver.observe(menuContainer.value) }) onUnmounted(() => { window.removeEventListener('resize', debouncedAdjust) }) </script>
3. 模板结构
<template> <div class="menu-container" ref="menuContainer"> <div v-for="item in mainMenuItems" :key="item.id" class="menu-item" > {{ item.label }} </div> <div class="menu-item more-btn" ref="moreBtn" @click="isMoreOpen = !isMoreOpen" > 更多 <span class="arrow">{{ isMoreOpen ? '▲' : '▼' }}</span> </div> <div v-if="isMoreOpen && moreMenuItems.length" class="more-dropdown"> <div v-for="item in moreMenuItems" :key="item.id" class="dropdown-item" > {{ item.label }} </div> </div> </div> </template>
4. 基础样式
<style scoped> .menu-container { display: flex; align-items: center; position: relative; background: #f5f5f5; padding: 0 16px; overflow: visible; } .menu-item { padding: 12px 16px; cursor: pointer; white-space: nowrap; } .more-btn { position: relative; } .more-dropdown { position: absolute; top: 100%; right: 16px; background: white; border: 1px solid #eee; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .dropdown-item { padding: 10px 16px; white-space: nowrap; cursor: pointer; } .dropdown-item:hover { background: #f5f5f5; } </style>
关键细节说明
- 防抖处理:避免窗口 resize 时频繁触发计算,减少性能消耗。
- ResizeObserver:相比仅监听
window.resize,能精准捕捉菜单容器自身的尺寸变化(比如父元素宽度调整)。 - 重置再计算:每次调整前先把所有项放回主菜单,确保屏幕宽度增大时,能重新判断哪些项可以回到主菜单,实现双向自适应。
- 白空格 nowrap:确保菜单项不换行,保证宽度计算的准确性。
内容的提问来源于stack exchange,提问作者uiHellen
相关产品推荐
相关产品推荐

