从零开始设计一个右键菜单组件

简介: 从零开始设计一个右键菜单组件

需求分析

首先要分析右键菜单需要实现什么功能

  • 点击鼠标右键弹出自定义的弹窗
  • 实现菜单项的点击
  • 自定义菜单项的样式
  • 自定义弹窗容器的样式

代码实现

需求搞定之后就是写代码了,下面是基础的代码框架

<template>
  <div class="yak-content-menu" @contextmenu="showContentMenuFn" @click="hideContextMenuFn">
    <slot></slot>
    <transition>
      <div
        v-show="visiable"
        class="yak-content-menu-wrap"
        :class="menuWrapClass"
        :style="{ left: menuPosition.left, top: menuPosition.top }"
      >
        <slot name="menu" :menuList="menus">
          <!-- 给于用户完整的菜单项控制权限 -->
          <span
            class="yak-content-menu-wrap-item"
            :class="menuItemClass"
            v-for="item in menus"
            :key="item.command"
            @click="menuClick(item)"
          >
            {{ item.text }}
          </span>
        </slot>
      </div>
    </transition>
  </div>
</template>
<script lang="ts">
import {
  ref,
  reactive,
  onMounted,
  onBeforeUnmount,
  defineComponent,
  PropType,
} from "vue";
interface MenuItem {
  command: string;
  text: string;
}
type Menus = Array<MenuItem>;
export default defineComponent({
  name: "YakContextmenu",
  props: {
    menus: {
      type: Array as PropType<Menus>,
      default: () => {
        return [];
      },
      required: true,
    }, // 菜单的数组
    menuWrapClass: String, // 菜单容器的自定义class
    menuItemClass: String, // 菜单项的自定义class
  },
  emits: ["menu-click"],
  setup(props, { emit }) {
    const wrapEl = ref();
    const visiable = ref(false);
    const menuPosition: any = reactive({
      left: 0,
      top: 0,
    });
    const showContextMenuFn = (ev: any) => {
      // 1:禁用默认的右键点击事件
      // 2:获取当前鼠标的位置
      // 3:控制弹窗的显示
    };
    const hideContextMenuFn = () => {
      // 隐藏菜单
    };
    const menuClick = (item: MenuItem) => {
      // 添加自定义事件 menu-click,方便组件使用
    };
    return {
      wrapEl,
      visiable,
      menuPosition,
      menuClick,
      showContentMenuFn,
      hideContextMenuFn,
    };
  },
});
</script>

实现 showContextMenuFn

有了上面的框架,现在让我们实现显示菜单的功能

const showContextMenuFn = (ev: any) => {
  // 禁用默认事件
  ev.preventDefault();
  // 获取自定义菜单的根元素的位置
  const rootPosition = wrapEl.value.getBoundingClientRect();
  // 用鼠标所在位置减去根元素的位置,就是弹窗元素相对于根元素的位置
  const x = ev.x - rootPosition.left;
  const y = ev.y - rootPosition.top;
  menuPosition.top = `${y}px`;
  menuPosition.left = `${x}px`;
  // 控制弹窗的显示
  visiable.value = true;
};

实现隐藏菜单和菜单点击

const hideContextMenuFn = () => {
  visiable.value = false; // 隐藏菜单
};
const menuClick = (item: MenuItem) => {
  emit("menu-click", item); // 添加自定义事件 menu-click
};

实现的效果

完整的代码

<template>
  <div class="yak-content-menu" @contextmenu="showContentMenuFn" @click="hideContextMenuFn">
    <slot></slot>
    <transition>
      <div
        v-show="visiable"
        class="yak-content-menu-wrap"
        :class="menuWrapClass"
        :style="{ left: menuPosition.left, top: menuPosition.top }"
      >
        <slot name="menu" :menuList="menus">
          <!-- 给于用户完整的菜单项控制权限 -->
          <span
            class="yak-content-menu-wrap-item"
            :class="menuItemClass"
            v-for="item in menus"
            :key="item.command"
            @click="menuClick(item)"
          >
            {{ item.text }}
          </span>
        </slot>
      </div>
    </transition>
  </div>
</template>
<script lang="ts">
import {
  ref,
  reactive,
  onMounted,
  onBeforeUnmount,
  defineComponent,
  PropType,
} from "vue";
interface MenuItem {
  command: string;
  text: string;
}
type Menus = Array<MenuItem>;
export default defineComponent({
  name: "YakContextmenu",
  props: {
    menus: {
      type: Array as PropType<Menus>,
      default: () => {
        return [];
      },
      required: true,
    }, // 菜单的数组
    menuWrapClass: String, // 菜单容器的自定义class
    menuItemClass: String, // 菜单项的自定义class
  },
  emits: ["menu-click"],
  setup(props, { emit }) {
    const wrapEl = ref();
    const visiable = ref(false);
    const menuPosition: any = reactive({
      left: 0,
      top: 0,
    });
    const showContextMenuFn = (ev: any) => {
      // 禁用默认事件
      ev.preventDefault();
      // 获取自定义菜单的根元素的位置
      const rootPosition = wrapEl.value.getBoundingClientRect();
      // 用鼠标所在位置减去根元素的位置,就是弹窗元素相对于根元素的位置
      const x = ev.x - rootPosition.left;
      const y = ev.y - rootPosition.top;
      menuPosition.top = `${y}px`;
      menuPosition.left = `${x}px`;
      // 控制弹窗的显示
      visiable.value = true;
    };
    const hideContextMenuFn = () => {
      visiable.value = false; // 隐藏菜单
    };
    const menuClick = (item: MenuItem) => {
      emit("menu-click", item); // 添加自定义事件 menu-click
    };
    return {
      wrapEl,
      visiable,
      menuPosition,
      menuClick,
      showContentMenuFn,
      hideContextMenuFn,
    };
  },
});
</script>
<style lang="scss">
.yak-content-menu {
  position: relative;
  &-wrap {
    display: inline-block;
    box-sizing: border-box;
    background-color: #fff;
    position: absolute;
    box-shadow: 0 1px 6px rgb(0 0 0 / 20%);
    border-color: 1px solid #eee;
    border-radius: 4px;
    padding: 5px 0;
    min-width: 160px;
    &-item {
      cursor: pointer;
      display: block;
      line-height: 28px;
      font-size: 14px;
      padding: 0 24px;
      text-align: left;
      &:hover {
        background-color: #eee;
      }
    }
  }
}

仓库地址 Github,如有需求,欢迎提交issue


相关文章
|
2月前
|
开发者 图形学 开发工具
Unity编辑器神级扩展攻略:从批量操作到定制Inspector界面,手把手教你编写高效开发工具,解锁编辑器隐藏潜能
【8月更文挑战第31天】Unity是一款强大的游戏开发引擎,支持多平台发布与高度可定制的编辑器环境。通过自定义编辑器工具,开发者能显著提升工作效率。本文介绍如何使用C#脚本扩展Unity编辑器功能,包括批量调整游戏对象位置、创建自定义Inspector界面及项目统计窗口等实用工具,并提供具体示例代码。理解并应用这些技巧,可大幅优化开发流程,提高生产力。
129 1
|
11月前
|
前端开发 C# 开发工具
Unity快手上手【熟悉unity编辑器,C#脚本控制组件一些属性之类的】
Unity快手上手【熟悉unity编辑器,C#脚本控制组件一些属性之类的】
145 0
|
前端开发
前端学习笔记202305学习笔记第二十三天-重构菜单组件2
前端学习笔记202305学习笔记第二十三天-重构菜单组件2
50 1
|
前端开发
前端学习笔记202305学习笔记第二十三天-重构菜单组件3
前端学习笔记202305学习笔记第二十三天-重构菜单组件3
51 0
|
前端开发
前端学习笔记202305学习笔记第二十三天-重构菜单组件1
前端学习笔记202305学习笔记第二十三天-重构菜单组件1
47 0
|
数据库 Android开发
重新构建711的Android项目(一),巧妙的小屏菜单查询框架实现
重新构建711的Android项目(一),巧妙的小屏菜单查询框架实现
最最最常用的就是按钮了吧~ — 常用组件详解(按钮系列)
普通的基础组件自然不能满足我们的日常开发需求,所以小T带大家了解Flutter开发中的常用组件。
最最最常用的就是按钮了吧~ — 常用组件详解(按钮系列)
|
JavaScript 前端开发 计算机视觉
【Vue3从零开始-实战】S15:详情页tab栏核心样式开发
【Vue3从零开始-实战】S15:详情页tab栏核心样式开发
336 0
【Vue3从零开始-实战】S15:详情页tab栏核心样式开发
|
计算机视觉
qml开发笔记(一):界面元素初探
qml开发笔记(一):界面元素初探
qml开发笔记(一):界面元素初探
|
Web App开发 前端开发
React 16.x折腾记 - (3) 结合Mobx实现一个比较靠谱的动态tab水平菜单,同时关联侧边栏
动态tab水平菜单,这个需求很常见,特别是对于后台管理系统来说 实现的思路有点绕,有更好的姿势请留言,谢谢阅读。
281 0
下一篇
无影云桌面