Vue3 如何实现一个函数式右键菜单(ContextMenus)

这篇具有很好参考价值的文章主要介绍了Vue3 如何实现一个函数式右键菜单(ContextMenus)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言: 最近在公司 PC 端的项目中使用到了右键出现菜单选项这样的一个工作需求,并且自己现在也在实现一个偶然迸发的 idea( 想用前端实现一个 windows 系统从开机到桌面的 UI),其中也要用到右键弹出菜单这样的一个功能,个人觉得这个实现还不错,特来分享🎁。

tips: 我个人是喜欢使用图文来讲解知识点的,相比于直接讲概念,我个人更倾向于使用费曼学习法来讲解某一个功能的实现过程,因为我也是刚从一只菜鸟走过来,所以我更加清楚一个新手在去学习一个全新的知识的时候,他其实不是需要你给他讲实现原理,而是你需要作为一个 “引路人” 让他先简单知道这个知识是用来干什么的,后面随着他自己一步一步的深入了解,他会自己慢慢领悟其中的原理。

一. 前期准备

  1. 我们需要清楚的认识到,这种用户点击右键然后弹出菜单的动作行为是非常不适合将组件写死在页面上,然后通过使用 v-show 或者 v-if 去控制它的出现和消失的,我们需要想办法使用函数式去控制它的行为。

  2. 在此之前,你需要准备两个文件来和我一起实现这个右键菜单。
    vue3 右键菜单,vue,vue.js,前端,javascript

  3. 预览图:
    vue3 右键菜单,vue,vue.js,前端,javascript

二. 右键菜单的样式

  1. 菜单样式的书写不是我们本文的重点,你可以快速在 Menu.vue 里简单书写你自己喜欢的一个简单 div 即可,我们的重点是在于如何右键弹出它。你也可以在下方的源码标题中直接复制我书写的样式,不过你需要使用 UnoCSS 来支持内敛样式属性。

  2. 如果你不知道如何使用 Unocss,你可以参考这篇文章的内容 手把手教你实现一个代码仓库里面有详细的过程来帮助你去完成代码仓库的构建,其中包括了 Unocss 如何引入和使用。)
    vue3 右键菜单,vue,vue.js,前端,javascript

三. h 函数 和 render 函数的使用

  1. 现在我们已经完成了 Menu.vue,文件的内容,接下来我们需要转头去书写 index.ts 内的内容。

  2. 在此之前,我们需要引入两个 vue 暴露给我们的,十分重要的函数。h,和 render
    vue3 右键菜单,vue,vue.js,前端,javascript

  3. 如果你之前读过我另外三篇文章,我相信你对这两个函数的使用一定不陌生,但是为了照顾之前没有了解过的读者,我还是会在接下来的内容中简单介绍一下。不过我还是建议你去看一看下面的实现方式,你一定会有不一样的收获。

  • Vue3 如何实现一个 Toast 小弹窗
  • Vue3 如何实现一个全局搜索框
  • Vue3 如何实现一个Dialog
  1. 接下来我简单的介绍一下,这两个函数的使用方式。你需要知道一个前提知识,我们在 template 标签里书写的样式,最终都会被转变成虚拟 dom
    vue3 右键菜单,vue,vue.js,前端,javascript
    这里面书写的 div 其实是和我们在浏览器里看到的 div “并不是同一个” div,只不过经过 vue 帮我们进行了处理,让它们的表现形式显得一样了。

  2. template 是经过了怎样的处理呢?其实就是经过了 h 函数。然后 h 函数会返回一个特殊的 JS 对象,这个特殊的对象就是我们所说的虚拟dom

  3. 那我们在这个场景怎么使用呢?首先你需要在 index.ts 文件内引入我们刚刚书写的右键菜单的样式。然后将这个组件作为 h 函数的第一个参数放入,对,就是这么简单。这个 vnode 就是我们需要用到的虚拟 dom
    vue3 右键菜单,vue,vue.js,前端,javascript

  4. 有了虚拟 dom 还不行,我们得告诉 vue 我们要把这个虚拟 dom 渲染到什么地方,这时候就需要用到 render 函数。render 函数要做的事情比较复杂,不过在这里你只需要简单的知道。render 函数会将一个 虚拟dom 转换成一个真实的 dom 节点。既然需要一个虚拟 dom,那我刚刚正好用 h 函数转换了得到了一个,于是我们自然而然可以写出下面的代码。
    vue3 右键菜单,vue,vue.js,前端,javascript

  5. 怎么回事?怎么还报错了呢?
    vue3 右键菜单,vue,vue.js,前端,javascript
    我们看一下报错信息,发现这个 render 函数需要两个参数,我们只给了一个。那么第二个参数是什么呢?我们思考一下,现在这个 dom 已经被转换成真实的 dom 节点了,但是目前它不知道自己应该被渲染到哪里,什么意思呢?其实理解起来很简单。
    就好比你现在是一个外卖员,你到了餐厅取餐,餐厅人员说你去吧,你端着手上的一份外卖餐一脸茫然,我去哪啊?
    就对应着,vue 帮你处理好了这个虚拟节点,但是你没告诉它应该在哪里去渲染。

  6. 知道原因就好办了,我们直接创建一个空的 div,先让 render 用着。
    vue3 右键菜单,vue,vue.js,前端,javascript

四. 右键弹出菜单的实现

  1. 在进行下面的功能之前,你需要知道一个前提知识。
    vue3 右键菜单,vue,vue.js,前端,javascript
    如上面的 gif 所示,我们可以看到,浏览器本身是存在默认的右键点击事件的。在这里我们需要取消浏览器自身的右键弹出菜单事件。

  2. 我们再具体一点讲,其实我们需要做的就是替换掉浏览器默认的右键事件。通过查阅 MDN 我们可以得知,window 对象存在一个叫做 contextMenus 的事件。
    vue3 右键菜单,vue,vue.js,前端,javascript

  3. 那接下来就好办了,我们直接替换这个事件为我们的自定义事件即可。(这里阻止默认事件需要调用 e.preventDefault 方法。)
    vue3 右键菜单,vue,vue.js,前端,javascript
    然后我们在随便一个全屏的组件引入这个函数,我们来测试一下,看看效果
    vue3 右键菜单,vue,vue.js,前端,javascript

  4. 嗯,现在已经不会弹出浏览器默认的菜单了。那么接下来要做的就是如何让我们写好的菜单呈现到页面上。首先第一点,我们需要明确告诉这个组件你的父元素是谁
    我们上面只是临时创造了一个简单的 div,但是目前我们还是没告诉它应该渲染到哪里。处理方法也很简单,这里我提前创建好了一个很简单的页面,并且设置好了一个唯一 ID
    vue3 右键菜单,vue,vue.js,前端,javascript

  5. 那么我们就可以非常轻松的获得这个元素。
    vue3 右键菜单,vue,vue.js,前端,javascript

  6. 现在父元素也有了,只需要将我们的 containerEl 元素放入到 scope 里即可。
    不过你需要知道的是,我们这个元素是不应该出现在正常的文档流里的,因为它的位置是不固定的,所以我们在放进去 scope 元素之前,应该给它处理成绝对定位类型的元素。
    vue3 右键菜单,vue,vue.js,前端,javascript

  7. 对了,这里需要注意,我们需要给 scope 设置一个 relative 属性,来告诉我们的 containerEl 它要在谁的范围内是绝对定位。
    vue3 右键菜单,vue,vue.js,前端,javascript

  8. 接下来我们进入到我们的 scope 组件内引入这个函数,调用一下看看效果。

    vue3 右键菜单,vue,vue.js,前端,javascript
    vue3 右键菜单,vue,vue.js,前端,javascript
    ok,现在已经实现我们的右键弹出菜单的基本功能了。

五. 菜单位置出现的位置

  1. 在这里我们需要用到 clientX,和 clientY 这两个属性。
    vue3 右键菜单,vue,vue.js,前端,javascript

  2. 如果你是第一次看到这个属性,那么我简单介绍一下。
    vue3 右键菜单,vue,vue.js,前端,javascript
    假设我在屏幕的上点击了一下(类比上图的红点出),那么此时这个点到屏幕最左边的距离就是 clientX,同理到屏幕顶部的距离就是 clientY

  3. 聪明的你一定想到了,那我此时将 containerEltopleft 的值分别设置成这两个属性的值,不就恰好会让菜单出现在我们的右边吗?我们试一下。
    vue3 右键菜单,vue,vue.js,前端,javascript
    然后看看效果:
    vue3 右键菜单,vue,vue.js,前端,javascript

  4. 目前看起来一切正常,但是我们需要考虑一个边界情况。
    vue3 右键菜单,vue,vue.js,前端,javascript
    当我们距离屏幕右侧过近的时候,此时右键会导致有部分内容被遮挡。所以我们要想办法解决这个边界情况。

六. 解决右侧过近的问题

  1. 不要觉得很难,其实目前我们要做的事情很简单。
    vue3 右键菜单,vue,vue.js,前端,javascript

  2. 如上图,我们仅仅只需要去判断
    scope 的 clientWidth 的长度 - clientX 的长度= 是否大于containerEl 的 offsetWidth ?
    如果大于,则调转 left 的方向为 right ,并设置 right=0px 即可。

  3. 如果上面所说的 offsetWidthclientWidth 你还不了解。我强烈建议你请点击这篇博文先去了解清楚这几个 width 属性到底代表着什么意思,因为对于前端开发来说,这是极其重要的几个属性。如果你之后要接触移动端,那么这是你必须掌握的知识点。
    你必须知道的 clientWdith,scrollWidth,offsetWidth

  4. 既然知道了原理,那么代码写起来就非常简单了,在此之前在这里我们需要调整一下 scope.appendChild 的执行时机。
    vue3 右键菜单,vue,vue.js,前端,javascript
    我们测试一下效果。
    vue3 右键菜单,vue,vue.js,前端,javascript

七. 增强该函数的健壮性

  1. 目前这个框我们无法确保它的唯一性,所以我们还需要改造一下这个函数。

  2. 增加一个变量 isShow ,我们需要知道当前的 Menu 菜单是否正在展示。
    vue3 右键菜单,vue,vue.js,前端,javascript

  3. containerElconst 声明变为 let 声明。并将创造时机延迟到调用右键时再创建,这样我们就能保证每次右键制造的这个 Menu 组件是都是全新的。(不然就会出现沿用上一次 css 属性,导致样式错乱的 bug )
    vue3 右键菜单,vue,vue.js,前端,javascript

  4. 获取 scope 元素的时机也推迟到用户点击右键的时候再获取。(因为下面的 close 函数也需要用到这个变量)
    vue3 右键菜单,vue,vue.js,前端,javascript

  5. 拆分两个函数,一个打开 openMenu 函数,一个关闭函数 closeMenu

    vue3 右键菜单,vue,vue.js,前端,javascript
    vue3 右键菜单,vue,vue.js,前端,javascript

  6. 最后在 window.oncontextmenu 的匿名函数里去调取这两个函数。
    vue3 右键菜单,vue,vue.js,前端,javascript

  7. 然后我们将这三个变量暴露出去。
    vue3 右键菜单,vue,vue.js,前端,javascript

八. 右键菜单的使用方法

  1. 我们进到 scope.vue 组件内,引入。
    vue3 右键菜单,vue,vue.js,前端,javascript

  2. 这样我们既可以通过右键创建这个菜单栏,也可以自己在合适的时间去做一些逻辑判断手动打开。

  3. 效果如下
    vue3 右键菜单,vue,vue.js,前端,javascript

源码

  1. Menu.vue 的源码。
<script lang="ts" setup>
import { ref } from "vue"

const menuItemsGroup = [
  {
    name: "查看(V)",
    arrow: true,
    action: () => {
      console.log("查看")
    },
  },
  {
    name: "排序方式(O)",
    arrow: false,
    action: () => {
      console.log("刷新")
    },
  },
  {
    name: "刷新(E)",
    arrow: false,
    action: () => {
      console.log("刷新")
    },
  },
  {
    name: "粘贴(P)",
    arrow: false,
    action: () => {
      console.log("刷新")
    },
  },
  {
    name: "粘贴快捷方式(S)",
    arrow: false,
    action: () => {
      console.log("刷新")
    },
  },
  {
    name: "新建(W)",
    arrow: false,
    action: () => {
      console.log("刷新")
    },
  },
  {
    name: "个性化(R)",
    arrow: false,
    action: () => {
      console.log("刷新")
    },
  },
]
</script>
<template>
  <div
    class="w-17rem bg-#ECECEC flex flex-col py-0.5rem shadow-[4px_4px_5px_2px_rgba(0,0,0,0.3)]"
  >
    <div
      v-for="(item, i) in menuItemsGroup"
      :key="i"
      @click="item.action"
      class="w-full h-2.5rem px-3rem text-1.5rem leading-2.5rem text-black hover:bg-white mb-0.3rem"
      :class="[3, 5, 6].includes(i) ? `b-t-1px b-gray` : `static`"
    >
      <span>{{ item.name }}</span>
    </div>
  </div>
</template>
  1. 这是 openContextMenus 的源码。
import { h, render } from "vue"

import Menu from "./Menu.vue"

export function openContextMenus() {
  let isShow = false
  let scope: HTMLElement | null // 拿到桌面元素
  let containerEl: HTMLDivElement // 创建一个容器元素,给 render 先用着

  window.oncontextmenu = function (e: MouseEvent) {
    e.preventDefault()
    if (isShow) closeMenu()
    openMenu(e)
  }

  //tips: open the menu
  function openMenu(e: MouseEvent) {
    scope = document.getElementById("PCDesktop")
    containerEl = document.createElement("div")
    const vnode = h(Menu)
    render(vnode, containerEl) //将 vnode 传递给 render 函数

    containerEl.style.position = "absolute"

    scope?.appendChild(containerEl) // 1. 为了拿到 offsetWidth,因为只有出现在浏览器才会产生 offsetWidth 属性值,我们需要先渲染出真实 dom

    const { offsetWidth } = containerEl //2 .取出 containerEl 的真实宽度
    const { clientWidth } = scope! //3. 获取父元素的 clientWidth 准备进行计算
    const { clientX, clientY } = e //4. 取出 click 时鼠标的坐标

    const _X = clientWidth - clientX > offsetWidth ? "left" : "right" //调整方向
    const _X_offset = clientWidth - clientX // 如果是需要显示在左边,则需要获取当前的差值

    containerEl.style.top = `${clientY}px`
    containerEl.style[_X] = _X === "left" ? `${clientX}px` : `${_X_offset}px`
    isShow = true
  }

  //tips: close the menu
  function closeMenu() {
    if (isShow) {
      render(null, containerEl)
      scope?.removeChild(containerEl)
      console.log("清楚")
      isShow = false
    }
  }
  return {
    isShow,
    openMenu,
    closeMenu,
  }
}

结语

最近在实现一个 window 的全套 UI ,代码开源到了 github
vue3 右键菜单,vue,vue.js,前端,javascript
我会在之后一直更新类似的内容,包括拖拽的实现。
如果你觉得本文对你有帮助,还希望点个赞

赠人玫瑰,手有余香🌹文章来源地址https://www.toymoban.com/news/detail-738796.html

到了这里,关于Vue3 如何实现一个函数式右键菜单(ContextMenus)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 用vue3+elementplus做的一个滚动菜单栏的组件

    在elementplus中看到了滚动条绑定了slider,但是这个感觉很不实用,在底部,而且横向滚动,最常见的应该是那种固定在左上角的带着菜单的滚动条,于是我就想要不做一个小demo,方便以后使用 样式如下:(背景是我父组件的背景色 首先不能用横着的滚动条,一开始我是想用

    2024年02月12日
    浏览(31)
  • vue3使用el-menu多级菜单出现点击一个全部展开的问题

            测试时发现单击菜单显示子菜单时其它的菜单也被展开,看了其它文章写的是修改:index=\\\"menu.index\\\",         虽然点击菜单其它的子菜单不会展开了,但是index存的是编号,url存的是路由地址,点击子菜单地址栏显示的是编号信息,不是地址,如图所示      

    2024年02月02日
    浏览(38)
  • 推荐一个基于.Net Framework开发的Windows右键菜单管理工具

    平常在我们电脑,我们都会安装非常多的软件,很多软件默认都会向系统注册右键菜单功能,这样方便我们快捷打开。比如图片文件,通过右键的方式,快捷选择PS软件打开。 如果我们电脑安装非常多的软件,就会导致我们右键菜单的列表非常多,但是很多软件我们是用不到

    2024年02月02日
    浏览(58)
  • vue3+elementui-plus实现无限递归菜单

    效果图 实现方式是:通过给定的数据结构层数来动态生成多级菜单 下面的方法可以实现重置菜单选项为默认项(需求场景:左侧菜单切换时,上方菜单就要重置为默认选项) 通过给el-menu添加:key=\\\"menuKey\\\"实现。 实现原理::key=“menuKey” 是 Vue 中的一个特殊语法,用于控制组件

    2024年04月24日
    浏览(31)
  • vue中,右键菜单组件v-contextmenu的使用

    vue中,右键菜单组件v-contextmenu的使用 1、效果 右键菜单之禁用和子菜单 2、流程 第一步:安包 第二步:引入 src/main.js package.json 第三步:使用 效果1-右键菜单之禁用和子菜单 index.vue 效果2-基本效果 index.vue 3、使用说明api npm地址——https://www.npmjs.com/package/v-contextmenu 另一个参

    2024年02月06日
    浏览(31)
  • Windows如何自定义右键新建菜单栏

    右键新建菜单的实现原理 参考文章 修改 win10 右键“新建”菜单(原理、两种方法及注意事项)_goocheez的博客-CSDN博客_右键新建菜单 默认情况下,win10 会在用户 每次单击右键后 ,系统弹出“新建”菜单之前,从注册表 计算机HKEY_CLASSES_ROOT 中的各个后缀中提取 ShellNew 分支,

    2024年02月06日
    浏览(24)
  • el-menu实现左侧菜单栏(vue2,vue3)

    vue3写法 多级数据 1. home.vue 先把页面基底搭建好 2. Sidebar.vue 左侧菜单栏(多级数据) 3. Header.vue 顶部导航栏 一级数据 home页面是一样的 Sidebar.vue Header.vue(我这里还做了一个切换昼夜间模式的主题) vue2 多级数据 一级数据(使用自己的图标

    2024年02月08日
    浏览(38)
  • (二) Vue3 + Element-Plus 实现动态菜单栏

    系列介绍:Vue3 + Vite + TS 从零开始学习 项目搭建:(一) Vue3 + Vite + TS 项目搭建 实现动态菜单栏:(二) Vue3 + Element-Plus 实现动态菜单栏 实现动态面包屑:(三) Vue3 + Element-Plus 实现动态面包屑 实现动态标签页:(四) Vue3 + Element-Plus 实现动态标签页 实现动态主题色切换(demo):(五)

    2023年04月23日
    浏览(43)
  • QTreewidget右键菜单功能实现

    QTreewidget有一个信号继承自QWidget的信号void QWidget::customContextMenuRequested(const QPoint pos);我们来看看官方介绍: 简单翻译一下:当widget的 contextMenuPolicy即上下文菜单属性是 Qt::CustomContextMenu,并且用户已request widget上的上下文菜单时(也就是点了右键),会发出此信号。位置 pos 是wi

    2024年02月16日
    浏览(25)
  • Vue单页面实现el-tree el-breadcrumb功能、el-tree右键点击树节点展示菜单功能、树节点编辑节点字段名称功能

    (1) 点击el-tree节点 使用el-breadcrumb展示选中树节点及父项数据         重点: handleNodeClick方法、getTreeNode方法 (2) 选择el-breadcrumb-item设置el-tree节点选中             必须设置属性: current-node-key=\\\"currentNodeKey\\\"  、 node-key=\\\"id\\\"           重点: 设置树节点渲染 this.$refs.tree.set

    2024年02月16日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包