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

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

需求分析

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

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

代码实现

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

<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月前
|
Web App开发 存储 前端开发
vue2精简方式实现鼠标在方框内拖拽效果源码
vue2精简方式实现鼠标在方框内拖拽效果源码
63 3
|
3月前
|
开发者 图形学 开发工具
Unity编辑器神级扩展攻略:从批量操作到定制Inspector界面,手把手教你编写高效开发工具,解锁编辑器隐藏潜能
【8月更文挑战第31天】Unity是一款强大的游戏开发引擎,支持多平台发布与高度可定制的编辑器环境。通过自定义编辑器工具,开发者能显著提升工作效率。本文介绍如何使用C#脚本扩展Unity编辑器功能,包括批量调整游戏对象位置、创建自定义Inspector界面及项目统计窗口等实用工具,并提供具体示例代码。理解并应用这些技巧,可大幅优化开发流程,提高生产力。
351 1
|
3月前
|
数据处理 开发者 C#
WPF数据绑定实战:从零开始,带你玩转数据与界面同步,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用开发中,数据绑定是核心技能之一,它能实现界面元素与数据源的同步更新。本文详细介绍了WPF数据绑定的概念与实现方法,包括属性绑定、元素绑定及路径绑定等技术,并通过示例代码展示了如何创建数据绑定。通过数据绑定,开发者不仅能简化代码、提高可维护性,还能提升用户体验。无论初学者还是有经验的开发者,都能从中受益,更好地掌握WPF数据绑定技巧。
85 0
|
前端开发
前端学习笔记202305学习笔记第二十三天-重构菜单组件2
前端学习笔记202305学习笔记第二十三天-重构菜单组件2
52 1
|
前端开发
前端学习笔记202305学习笔记第二十三天-重构菜单组件3
前端学习笔记202305学习笔记第二十三天-重构菜单组件3
54 0
|
前端开发
前端学习笔记202305学习笔记第二十三天-重构菜单组件1
前端学习笔记202305学习笔记第二十三天-重构菜单组件1
51 0
|
数据库 Android开发
重新构建711的Android项目(一),巧妙的小屏菜单查询框架实现
重新构建711的Android项目(一),巧妙的小屏菜单查询框架实现
|
JavaScript 前端开发
原生js制作选项卡详解,适合无基础的人学习
原生js制作选项卡详解,适合无基础的人学习
140 0
原生js制作选项卡详解,适合无基础的人学习
|
JavaScript 前端开发 计算机视觉
【Vue3从零开始-实战】S15:详情页tab栏核心样式开发
【Vue3从零开始-实战】S15:详情页tab栏核心样式开发
351 0
【Vue3从零开始-实战】S15:详情页tab栏核心样式开发
|
计算机视觉
qml开发笔记(一):界面元素初探
qml开发笔记(一):界面元素初探
qml开发笔记(一):界面元素初探