商城网站建设实训报告模板,建盏是什么意思,怎么做网站的百度权重,平面设计毕业设计作品前言: 最近在公司 PC 端的项目中使用到了右键出现菜单选项这样的一个工作需求#xff0c;并且自己现在也在实现一个偶然迸发的 idea#xff08; 想用前端实现一个 windows 系统从开机到桌面的 UI#xff09;#xff0c;其中也要用到右键弹出菜单这样的一个功能#xff0c;…前言: 最近在公司 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 ,并设置 right0px 即可。 如果上面所说的 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 langts setup
import { ref } from vueconst 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
templatedivclassw-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:keyiclickitem.actionclassw-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 : staticspan{{ item.name }}/span/div/div
/template这是 openContextMenus 的源码。
import { h, render } from vueimport Menu from ./Menu.vueexport 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 absolutescope?.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}pxcontainerEl.style[_X] _X left ? ${clientX}px : ${_X_offset}pxisShow 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。 我会在之后一直更新类似的内容包括拖拽的实现。 如果你觉得本文对你有帮助还希望点个赞
赠人玫瑰手有余香