专业网站建设哪里找/网站服务器ip查询
前言: 最近在公司 PC 端的项目中使用到了右键出现菜单选项这样的一个工作需求,并且自己现在也在实现一个偶然迸发的 idea( 想用前端实现一个 windows 系统从开机到桌面的 UI),其中也要用到右键弹出菜单这样的一个功能,个人觉得这个实现还不错,特来分享🎁。
tips: 我个人是喜欢使用图文来讲解知识点的,相比于直接讲概念,我个人更倾向于使用费曼学习法来讲解某一个功能的实现过程,因为我也是刚从一只菜鸟走过来,所以我更加清楚一个新手在去学习一个全新的知识的时候,他其实不是需要你给他讲实现原理,而是你需要作为一个 “引路人” 让他先简单知道这个知识是用来干什么的,后面随着他自己一步一步的深入了解,他会自己慢慢领悟其中的原理。
一. 前期准备
-
我们需要清楚的认识到,这种用户点击右键然后弹出菜单的动作行为是非常不适合将组件写死在页面上,然后通过使用
v-show
或者v-if
去控制它的出现和消失的,我们需要想办法使用函数式去控制它的行为。 -
在此之前,你需要准备两个文件来和我一起实现这个右键菜单。
-
预览图:
二. 右键菜单的样式
-
菜单样式的书写不是我们本文的重点,你可以快速在 Menu.vue 里简单书写你自己喜欢的一个简单 div 即可,我们的重点是在于如何右键弹出它。你也可以在下方的源码标题中直接复制我书写的样式,不过你需要使用
UnoCSS
来支持内敛样式属性。 -
如果你不知道如何使用
Unocss
,你可以参考这篇文章的内容 手把手教你实现一个代码仓库里面有详细的过程来帮助你去完成代码仓库的构建,其中包括了Unocss
如何引入和使用。)
三. h 函数 和 render 函数的使用
-
现在我们已经完成了
Menu.vue
,文件的内容,接下来我们需要转头去书写index.ts
内的内容。 -
在此之前,我们需要引入两个
vue
暴露给我们的,十分重要的函数。h,和 render
。
-
如果你之前读过我另外三篇文章,我相信你对这两个函数的使用一定不陌生,但是为了照顾之前没有了解过的读者,我还是会在接下来的内容中简单介绍一下。不过我还是建议你去看一看下面的实现方式,你一定会有不一样的收获。
- Vue3 如何实现一个 Toast 小弹窗
- Vue3 如何实现一个全局搜索框
- Vue3 如何实现一个Dialog
-
接下来我简单的介绍一下,这两个函数的使用方式。你需要知道一个前提知识,我们在
template
标签里书写的样式,最终都会被转变成虚拟dom
。
这里面书写的div
其实是和我们在浏览器里看到的div
“并不是同一个”div
,只不过经过vue
帮我们进行了处理,让它们的表现形式显得一样了。 -
那
template
是经过了怎样的处理呢?其实就是经过了h
函数。然后h
函数会返回一个特殊的 JS 对象,这个特殊的对象就是我们所说的虚拟dom。 -
那我们在这个场景怎么使用呢?首先你需要在
index.ts
文件内引入我们刚刚书写的右键菜单的样式。然后将这个组件作为h
函数的第一个参数放入,对,就是这么简单。这个vnode
就是我们需要用到的虚拟 dom。
-
有了虚拟 dom 还不行,我们得告诉 vue 我们要把这个虚拟 dom 渲染到什么地方,这时候就需要用到
render
函数。render
函数要做的事情比较复杂,不过在这里你只需要简单的知道。render
函数会将一个 虚拟dom 转换成一个真实的 dom 节点。既然需要一个虚拟 dom,那我刚刚正好用h
函数转换了得到了一个,于是我们自然而然可以写出下面的代码。
-
怎么回事?怎么还报错了呢?
我们看一下报错信息,发现这个render
函数需要两个参数,我们只给了一个。那么第二个参数是什么呢?我们思考一下,现在这个dom
已经被转换成真实的 dom 节点了,但是目前它不知道自己应该被渲染到哪里,什么意思呢?其实理解起来很简单。
就好比你现在是一个外卖员,你到了餐厅取餐,餐厅人员说你去吧,你端着手上的一份外卖餐一脸茫然,我去哪啊?
就对应着,vue 帮你处理好了这个虚拟节点,但是你没告诉它应该在哪里去渲染。 -
知道原因就好办了,我们直接创建一个空的
div
,先让render
用着。
四. 右键弹出菜单的实现
-
在进行下面的功能之前,你需要知道一个前提知识。
如上面的gif
所示,我们可以看到,浏览器本身是存在默认的右键点击事件的。在这里我们需要取消浏览器自身的右键弹出菜单事件。 -
我们再具体一点讲,其实我们需要做的就是替换掉浏览器默认的右键事件。通过查阅 MDN 我们可以得知,window 对象存在一个叫做
contextMenus
的事件。
-
那接下来就好办了,我们直接替换这个事件为我们的自定义事件即可。(这里阻止默认事件需要调用 e.preventDefault 方法。)
然后我们在随便一个全屏的组件引入这个函数,我们来测试一下,看看效果
-
嗯,现在已经不会弹出浏览器默认的菜单了。那么接下来要做的就是如何让我们写好的菜单呈现到页面上。首先第一点,我们需要明确告诉这个组件你的父元素是谁。
我们上面只是临时创造了一个简单的div
,但是目前我们还是没告诉它应该渲染到哪里。处理方法也很简单,这里我提前创建好了一个很简单的页面,并且设置好了一个唯一 ID。
-
那么我们就可以非常轻松的获得这个元素。
-
现在父元素也有了,只需要将我们的
containerEl
元素放入到scope
里即可。
不过你需要知道的是,我们这个元素是不应该出现在正常的文档流里的,因为它的位置是不固定的,所以我们在放进去scope
元素之前,应该给它处理成绝对定位类型的元素。
-
对了,这里需要注意,我们需要给
scope
设置一个relative
属性,来告诉我们的containerEl
它要在谁的范围内是绝对定位。
-
接下来我们进入到我们的
scope
组件内引入这个函数,调用一下看看效果。
ok,现在已经实现我们的右键弹出菜单的基本功能了。
五. 菜单位置出现的位置
-
在这里我们需要用到
clientX,和 clientY
这两个属性。
-
如果你是第一次看到这个属性,那么我简单介绍一下。
假设我在屏幕的上点击了一下(类比上图的红点出),那么此时这个点到屏幕最左边的距离就是clientX
,同理到屏幕顶部的距离就是clientY
。 -
聪明的你一定想到了,那我此时将
containerEl
的top
和left
的值分别设置成这两个属性的值,不就恰好会让菜单出现在我们的右边吗?我们试一下。
然后看看效果:
-
目前看起来一切正常,但是我们需要考虑一个边界情况。
当我们距离屏幕右侧过近的时候,此时右键会导致有部分内容被遮挡。所以我们要想办法解决这个边界情况。
六. 解决右侧过近的问题
-
不要觉得很难,其实目前我们要做的事情很简单。
-
如上图,我们仅仅只需要去判断
scope 的 clientWidth 的长度 - clientX 的长度= 是否大于containerEl 的 offsetWidth ?
如果大于,则调转left
的方向为right
,并设置right=0px
即可。 -
如果上面所说的
offsetWidth
和clientWidth
你还不了解。我强烈建议你请点击这篇博文先去了解清楚这几个width
属性到底代表着什么意思,因为对于前端开发来说,这是极其重要的几个属性。如果你之后要接触移动端,那么这是你必须掌握的知识点。
你必须知道的 clientWdith,scrollWidth,offsetWidth -
既然知道了原理,那么代码写起来就非常简单了,在此之前在这里我们需要调整一下
scope.appendChild
的执行时机。
我们测试一下效果。
七. 增强该函数的健壮性
-
目前这个框我们无法确保它的唯一性,所以我们还需要改造一下这个函数。
-
增加一个变量
isShow
,我们需要知道当前的Menu
菜单是否正在展示。
-
将
containerEl
由const
声明变为let
声明。并将创造时机延迟到调用右键时再创建,这样我们就能保证每次右键制造的这个Menu
组件是都是全新的。(不然就会出现沿用上一次 css 属性,导致样式错乱的 bug )
-
获取
scope
元素的时机也推迟到用户点击右键的时候再获取。(因为下面的close
函数也需要用到这个变量)
-
拆分两个函数,一个打开
openMenu
函数,一个关闭函数closeMenu
。
-
最后在
window.oncontextmenu
的匿名函数里去调取这两个函数。
-
然后我们将这三个变量暴露出去。
八. 右键菜单的使用方法
-
我们进到
scope
的.vue
组件内,引入。
-
这样我们既可以通过右键创建这个菜单栏,也可以自己在合适的时间去做一些逻辑判断手动打开。
-
效果如下
源码
- 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><divclass="w-17rem bg-#ECECEC flex flex-col py-0.5rem shadow-[4px_4px_5px_2px_rgba(0,0,0,0.3)]"><divv-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>
- 这是 openContextMenus 的源码。
import { h, render } from "vue"import Menu from "./Menu.vue"export function openContextMenus() {let isShow = falselet scope: HTMLElement | null // 拿到桌面元素let containerEl: HTMLDivElement // 创建一个容器元素,给 render 先用着window.oncontextmenu = function (e: MouseEvent) {e.preventDefault()if (isShow) closeMenu()openMenu(e)}//tips: open the menufunction 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 属性值,我们需要先渲染出真实 domconst { 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 menufunction closeMenu() {if (isShow) {render(null, containerEl)scope?.removeChild(containerEl)console.log("清楚")isShow = false}}return {isShow,openMenu,closeMenu,}
}
结语
最近在实现一个 window
的全套 UI
,代码开源到了 github
。
我会在之后一直更新类似的内容,包括拖拽的实现。
如果你觉得本文对你有帮助,还希望点个赞
赠人玫瑰,手有余香🌹
相关文章:

Vue3 如何实现一个函数式右键菜单(ContextMenus)
前言: 最近在公司 PC 端的项目中使用到了右键出现菜单选项这样的一个工作需求,并且自己现在也在实现一个偶然迸发的 idea( 想用前端实现一个 windows 系统从开机到桌面的 UI),其中也要用到右键弹出菜单这样的一个功能,…...

ffmpeg转码转封装小工具开发
如下图所示,是本人开发的一个转码转封装小工具 其中目标文件视频编码格式支持:H264,H265,VP8,VP9。 目标文件封装格式支持:mp4,mkv,avi,mov,flv。 目标文件音频编码格式支持两个,COPY和AAC&am…...

重入和线程安全
在整个文档中,重入和线程安全用于标记类和函数,从而表明怎样在多线程应用中使用它们。 线程安全函数可以从多个线程同时调用,即使调用使用共享数据也是如此,因为对共享数据的所有引用都是序列化的。也可以从多个线程同时调用重入…...

MySQL数据库06——条件查询(WHERE)
MySQL条件查询,主要是对数据库里面的数据按照一定条件进行筛选,主要依靠的是WHERE语句进行。 先来了解一下基础的条件运算。 关系运算符 逻辑运算符 逻辑运算符优先级:NOT>AND>OR,关系运算符>逻辑运算符 SQL特殊运算符…...

Lesson 6.5 机器学习调参基础理论与网格搜索
文章目录一、机器学习调参理论基础1. 机器学习调参目标及基本方法2. 基于网格搜索的超参数的调整方法2.1 参数空间2.2 交叉验证与评估指标二、基于 Scikit-Learn 的网格搜索调参1. sklearn 中网格搜索的基本说明2. sklearn 中 GridSearchCV 的参数解释3. sklearn 中 GridSearch…...

leetcode: Two Sum
leetcode: Two Sum1. 题目1.1 题目描述2. 解答2.1 baseline2.2 基于baseline的思考2.3 优化思路的实施2.3.1 C中的hashmap2.3.2 实施2.3.3 再思考2.3.4 最终实施3. 总结1. 题目 1.1 题目描述 Given an array of integers nums and an integer target, return indices of the …...

共享模型之无锁(三)
1.原子累加器 示例代码: public class TestAtomicAdder {public static void main(String[] args) {for (int i 0; i < 5; i) {demo(() -> new AtomicLong(0),(adder) -> adder.getAndIncrement());}for (int i 0; i < 5; i) {demo(() -> new LongAdder(),(…...

微信小程序 Springboot校运会高校运动会管理系统
3.1小程序端 小程序登录页面,用户也可以在此页面进行注册并且登录等。 登录成功后可以在我的个人中心查看自己的个人信息或者修改信息等 在广播信息中我们可以查看校运会发布的一些信息情况。 在首页我们可以看到校运会具体有什么项目运动。 在查看具体有什么活动我…...

走进独自开,带你轻松干副业
今天给大家分享一个开发者的福利平台——独自开(点击直接注册),让你在家就能解决收入问题。 文章目录一、平台介绍二、系统案例三、获取收益四、使用平台1、用户注册2、用户认证3、任务报价五、文末总结一、平台介绍 简单说明 独自开信息科技…...

SpringBoot+Vue实现师生健康信息管理系统
文末获取源码 开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7/8.0 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven包:Maven3.3.9 浏…...

数据库第四章节第三次作业内容
1、显示所有职工的基本信息。 2、查询所有职工所属部门的部门号,不显示重复的部门号。 3、求出所有职工的人数。 4、列出最高工和最低工资。 5、列出职工的平均工资和总工资。 6、创建一个只有职工号、姓名和参加工作的新表,名为工作日期表…...

一篇五分生信临床模型预测文章代码复现——FIgure 9.列线图构建,ROC分析,DCA分析 (四)
之前讲过临床模型预测的专栏,但那只是基础版本,下面我们以自噬相关基因为例子,模仿一篇五分文章,将图和代码复现出来,学会本专栏课程,可以具备发一篇五分左右文章的水平: 本专栏目录如下: Figure 1:差异表达基因及预后基因筛选(图片仅供参考) Figure 2. 生存分析,…...

神经网络实战--使用迁移学习完成猫狗分类
前言: Hello大家好,我是Dream。 今天来学习一下如何使用基于tensorflow和keras的迁移学习完成猫狗分类,欢迎大家一起前来探讨学习~ 本文目录:一、加载数据集1.调用库函数2.加载数据集3.数据集管理二、猫狗数据集介绍1.猫狗数据集介…...

Attention机制 学习笔记
学习自https://easyai.tech/ai-definition/attention/ Attention本质 Attention(注意力)机制如果浅层的理解,跟他的名字非常匹配。他的核心逻辑就是“从关注全部到关注重点”。 比如我们人在看图片时,对图片的不同地方的注意力…...

数据类型与运算符
1.字符型作用: 字符型变量用于显示单个字符语法: char cc a ;注意1: 在显示字符型变量时,用单引号将字符括起来,不要用双引号注意2: 单引号内只能有一个字符,不可以是字符串C和C中字符型变量只占用1个字节。字符型变是并不是把字符本身放到内存中存储&am…...

算法刷题-二叉树的锯齿形层序遍历、用栈实现队列 栈设计、买卖股票的最佳时机 IV
文章目录二叉树的锯齿形层序遍历(树、广度优先搜索)用栈实现队列(栈、设计)买卖股票的最佳时机 IV(数组、动态规划)二叉树的锯齿形层序遍历(树、广度优先搜索) 给定一个二叉树&…...

华为OD机试 - 最小传递延迟(Python)| 代码编写思路+核心知识点
最小传递延迟 题目 通讯网络中有 N 个网络节点 用 1 ~ N 进行标识 网络通过一个有向无环图进行表示 其中图的边的值,表示节点之间的消息传递延迟 现给定相连节点之间的延时列表 times[i]={u,v,w} 其中 u 表示源节点,v 表示目的节点,w 表示 u 和 v 之间的消息传递延时 请计…...

集中供热调度系统天然气仪表内网仪表图像识别案例
一、项目需求 出于能耗采集与冬季集中供暖工作的节能和能耗分析需要,要采集现场的6块天然气表计,并存储进入客户的mySQL数据库中,现场采集的表计不允许接线,且网络环境为内网环境,需要采集表计数据并存入数据库&#…...

笔试题-2023-复旦微-数字IC设计【纯净题目版】
回到首页:2023 数字IC设计秋招复盘——数十家公司笔试题、面试实录 推荐内容:数字IC设计学习比较实用的资料推荐 题目背景 笔试时间:2022.07.26应聘岗位:数字前端工程师笔试时长:120min笔试平台:赛码题目类型:基础题(10道)、选做题(10道)、验证题(5道)主观评价 难…...

【Linux】冯诺依曼体系结构和操作系统概念
文章目录🎪 冯诺依曼体系结构🚀1.体系概述🚀2.CPU和内存的数据交换🚀3.体系结构中数据的流动🎪 操作系统概念理解🚀1.简述🚀2.设计目的🚀3.定位🚀4.理解🚀5.管…...

HTML5之HTML基础学习笔记
列表标签 列表的应用场景 场景:在网页中按照行展示关联性的内容,如:新闻列表、排行榜、账单等特点:按照行的方式,整齐显示内容种类:无序列表、有序列表、自定义列表 这是老师PPT上的内容, 列表…...

FreeRTOS信号量 | FreeRTOS十
目录 说明: 一、信号量 1.1、信号量简介 1.2、信号量特点 二、二值信号量 2.1、二值信号量简介 2.2、获取与释放二值信号量函数 2.3、二值信号量使用过程与相关API函数 2.4、创建二值信号量函数了解 2.5、释放二值信号量了解 2.6、获取二值信号量了解 三…...

【SpringBoot】SpringBoot常用注解
一、前言首先这里说的SpringBoot常用注解是指在我们开发项目过程中,我们经常使用的注解,包含Spring、SpringBoot、SpringCloud、SpringMVC等这些框架中的注解,而不仅仅是SpringBoot中的注解。这里只是作一个注解列举,每个注解具体…...

数据一致性
目录一、AOP 动态代理切入方法(1) Aspect Oriented Programming(2) 切入点表达式二、SpringBoot 项目扫描类(1) ResourceLoader 扫描类(2) Map 的 computeIfAbsent 方法(3) 反射几个常用 api① 创建一个测试注解② 创建测试 PO 类③ 反射 api 获取指定类的指定注解信息(4) 返回…...

Docker不做虚拟化内核,对.NET有什么影响?
引子前两天刷抖音,看见了这样一个问题。问题:容器化不做虚拟内核,会有什么弊端?Java很多方法会跟CPU的核数有关,这个时候调用系统函数,读到的是宿主机信息,而不是我们限制资源的大小。思考&…...

HTML总结
CSS代码风格 空格规范: 1. 属性值前面,冒号后面,保留一个空格; 2. 选择器(标签)和大括号中间保留空格。 基本语法概述: 1.HTML标签是由尖括号包围的关键词,如<html> 2.HTM…...

ByteHouse:基于ClickHouse的实时数仓能力升级解读
更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群 ByteHouse是火山引擎上的一款云原生数据仓库,为用户带来极速分析体验,能够支撑实时数据分析和海量数据离线分析。便捷的弹性扩缩容能力&…...

[SSD固态硬盘技术 15] FTL映射表的神秘面纱
为什么需要映射表?固态硬盘的存储器件采用的是闪存[5],具有以下几个特点: (1)读写基本单位是以页(Page)为单位,擦除是以块(Block)为单位。...

浅析依赖注入框架的生命周期(以 InversifyJS 为例)
在上一篇介绍了 VSCode 的依赖注入设计,并且实现了一个简单的 IOC 框架。但是距离成为一个生产环境可用的框架还差的很远。 行业内已经有许多非常优秀的开源 IOC 框架,它们划分了更为清晰地模块来应对复杂情况下依赖注入运行的正确性。 这里我将以 Inv…...

HER2靶向药物研发进展-销售数据-上市药品前景分析
HER2长期作为肿瘤领域的热门靶点之一,其原因是它在多部位、多种形式的癌症中均有异常的表达,据研究表明HER2除了在胃癌、胆道癌、胆管癌、乳腺癌、卵巢癌、结肠癌、膀胱癌、肺癌、子宫颈癌、子宫浆液性子宫内膜癌、头颈癌、食道癌中的异常表达还存在于多…...