Vue 3 右键菜单显示隐藏实现:基于 tabindex 和 blur 事件的优雅解决方案
Published on Jul 01, 2025, with 41 view(s) and 0 comment(s)

实现的核心在于两点:

  1. tabindex 属性:使菜单元素能够接收焦点
  2. blur 事件:当菜单失去焦点时自动关闭

优势在于:

  • 代码简洁,逻辑清晰
  • 页面中存在多个右键菜单时不需要来回进行开关操作
  • 无需复杂的 DOM 事件监听
  • 符合用户直觉(点击其他地方菜单消失)

代码实例:

<template>
    <div @contextmenu.prevent="openContextMenu">
        <!-- 页面内容 -->
        <div>右键点击此处显示菜单</div>

        <!-- 右键菜单 -->
        <div v-if="showMenu" ref="menuRef" tabindex="-1" class="context-menu"
            :style="{ left: `${position.x}px`, top: `${position.y}px` }" @blur="showMenu = false">
            <div class="menu-item" @click="handleAction('copy')">复制</div>
            <div class="menu-item" @click="handleAction('paste')">粘贴</div>
            <div class="menu-item" @click="handleAction('delete')">删除</div>
        </div>
    </div>
</template>

<script setup>
import { ref, nextTick } from 'vue';

const showMenu = ref(false);
const position = ref({ x: 0, y: 0 });
const menuRef = ref(null);

const openContextMenu = (e) => {
    showMenu.value = true;
    position.value = { x: e.clientX, y: e.clientY };

    nextTick(() => {
        menuRef.value?.focus();
    });
};

const handleAction = (action) => {
    console.log('执行操作:', action);
};
</script>

<style>
.context-menu {
    position: fixed;
    background: white;
    border: 1px solid #ddd;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    z-index: 1000;
    min-width: 120px;
    outline: none;
    /* 移除焦点时的轮廓 */
}

.menu-item {
    padding: 8px 12px;
    cursor: pointer;
}

.menu-item:hover {
    background: #f5f5f5;
}
</style>