Vue2+Vuetify:如何让v-menu在选中项处居中?
解决方案
方案一:不依赖auto属性,实现选中项居中
如果不需要auto的自动避溢功能,直接关闭auto,通过滚动控制让选中项居中:
- 给
v-list-item-group绑定选中状态,监听选中变化事件 - 菜单打开后,找到选中项的DOM元素,调用
scrollIntoView让它滚动到列表容器中央
示例代码:
<template> <v-menu v-model="menuOpen" :close-on-content-click="false" offset-y <!-- 强制菜单固定在触发元素下方 --> > <template v-slot:activator="{ props }"> <v-text-field v-bind="props" label="选择项" :value="selectedItem" /> </template> <v-list style="max-height: 300px; overflow-y: auto;" <!-- 固定列表高度,显示滚动条 --> > <v-list-item-group v-model="selectedItem" @update:model-value="scrollToCenter" > <v-list-item v-for="item in items" :key="item.id" :value="item.id" > <v-list-item-title>{{ item.name }}</v-list-item-title> </v-list-item> </v-list-item-group> </v-list> </v-menu> </template> <script setup> import { ref, nextTick } from 'vue' const menuOpen = ref(false) const selectedItem = ref(1) const items = ref(Array.from({length: 50}, (_, i) => ({id: i+1, name: `选项 ${i+1}`}))) const scrollToCenter = async () => { await nextTick() // 等待DOM更新完成 const activeItem = document.querySelector('.v-list-item-group--active') if (activeItem) { activeItem.scrollIntoView({ block: 'center', behavior: 'smooth' }) } } // 菜单打开时触发滚动,确保初始选中项也居中 const handleMenuOpen = async () => { await nextTick() scrollToCenter() } </script>
关键说明:
- 用
offset-y替代auto,强制菜单贴在触发元素下方 - 列表容器设置固定高度和滚动属性,通过
scrollIntoView({block: 'center'})实现选中项居中 - 监听菜单打开事件,保证初始状态选中项也能居中显示
方案二:保留auto属性,修复定位问题
如果必须依赖auto的避溢功能,可通过以下方式强制菜单优先显示在下方,并调整选中项居中:
- 启用
auto,同时设置offset-y和min-height,约束菜单的最小高度,减少Vuetify自动向上调整的概率 - 监听菜单的
opened事件,手动计算触发元素位置,覆盖auto的自动定位 - 同样用
scrollIntoView实现选中项居中
示例代码:
<template> <v-menu v-model="menuOpen" auto offset-y :min-height="300" @opened="adjustMenuPosition" > <template v-slot:activator="{ props, attrs }"> <v-text-field v-bind="props" v-bind="attrs" label="选择项" :value="selectedItem" ref="trigger" /> </template> <v-list style="max-height: 300px; overflow-y: auto;" ref="listContainer" > <v-list-item-group v-model="selectedItem" @update:model-value="scrollToCenter" > <v-list-item v-for="item in items" :key="item.id" :value="item.id" > <v-list-item-title>{{ item.name }}</v-list-item-title> </v-list-item> </v-list-item-group> </v-list> </v-menu> </template> <script setup> import { ref, nextTick } from 'vue' const menuOpen = ref(false) const selectedItem = ref(1) const trigger = ref(null) const items = ref(Array.from({length: 50}, (_, i) => ({id: i+1, name: `选项 ${i+1}`}))) const scrollToCenter = async () => { await nextTick() const activeItem = document.querySelector('.v-list-item-group--active') if (activeItem) { activeItem.scrollIntoView({ block: 'center', behavior: 'smooth' }) } } const adjustMenuPosition = async () => { await nextTick() const triggerRect = trigger.value.getBoundingClientRect() const menuEl = document.querySelector('.v-menu__content') if (menuEl && triggerRect) { // 强制菜单定位在触发元素下方,覆盖auto的自动调整 menuEl.style.top = `${triggerRect.bottom + window.scrollY}px` menuEl.style.left = `${triggerRect.left + window.scrollX}px` // 移除auto添加的transform属性,避免定位冲突 menuEl.style.transform = 'none' } scrollToCenter() } </script>
关键说明:
offset-y和min-height能引导Vuetify优先将菜单放在触发元素下方- 通过
getBoundingClientRect()获取触发元素的视窗位置,手动设置菜单的top和left,同时加上滚动偏移量确保定位准确 - 移除菜单的
transform属性,避免和手动定位逻辑冲突
额外注意事项
auto属性会直接覆盖nudge-right/nudge-bottom的偏移设置,两者不要同时使用- 菜单挂载在body上时,计算位置必须加上
window.scrollX/window.scrollY,避免滚动页面后定位偏移 - 可添加
persistent属性临时关闭菜单自动关闭,方便调试定位问题
内容的提问来源于stack exchange,提问作者Lissi
相关产品推荐
相关产品推荐

