You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 18:42:19