当前位置: 首页 > 科技观察

前端实现右键自定义菜单

时间:2023-03-13 13:06:57 科技观察

大家好,我是前端西瓜哥。本文将讲解如何在网页上实现自定义菜单功能。在线演示:https://codepen.io/F-star/pen/WNOvQVQ。核心思想是:注册contextmenu事件,取消该事件的默认行为,然后通过事件对象获取光标相对视口的坐标位置(event.clientX和event.clientY),使用绝对定位设置自定义的方法显示了最初不可见的div块。实现DOM结构首先是DOM结构。结构如下:div.page-view是注册contextmenu事件的元素。div.contextmenu-mask是覆盖整个窗口的遮罩层。与右键菜单一起出现,其作用是防止用户在调出右键菜单后点击菜单外的按钮。此外,还可以添加带透明度的背景色,但效果类似于弹窗。一般来说,不设置背景颜色。div.contextmenu-content上下文菜单的内容。点击区域

复制
剪切
粘贴粘贴粘贴粘贴粘贴粘贴粘贴全选CSSstyle.page-view{margin:0auto;宽度:90%;height:calc(100vh-30px);background-color:azure;}/*遮罩层*/.contextmenu-mask{position:fixed;左:0;右:0;顶部:0;底部:0;/*背景颜色:#000;*//*不透明度:.2;*/z-index:45;}/*菜单内容的容器*/.contextmenu-content{position:fixed;左:999999px;顶部:999999px;z-指数:50;user-select:none;}/*Examplesusecontent*/.list{border:1pxsolid#555;边界半径:4px;最小宽度:180px;溢出:隐藏;/*处理圆角*/}.item{box-sizing:border-box;填充:05px;高度:30px;行高高度:30px;分词:保留所有;/*非常重要,否则会中断*/background-color:#fff;cursor:default;}.item:hover{background-color:dodgerblue;颜色:#fff;这里有几点需要注意:contextmenu-content并没有被display:none隐藏,而是通过设置非常大的left和top远远超出了窗口。这是有原因的,我们将在后面详细解释脚本逻辑。item需要设置分词:keep-all;.因为当菜单跑出窗口后,宽度会变成最小宽度,本例为180px。只有设置这个属性和值,才能让文字不换行,得到我们想要的宽度。contextmenu-content需要使用固定定位,而不是绝对定位。因为left和top元素设置了很大的值,对于没有overflow:hidden的容器元素,会产生很长的滚动条。固定目标没有。脚本逻辑右键显示菜单首先取消了点击区域菜单事件的默认行为。获取光标的坐标,为了防止菜单部分跑出窗口,导致被切掉,需要调整坐标。为此,我们需要获取菜单的宽高和窗口可见区域的宽高。另外,为了防止菜单边缘贴在窗口边缘,效果不美观,需要设置一个最小padding值参与计算。Truncatedmenu:靠近窗口边缘的菜单:以设置横坐标为例,有:这里代码的含义是的:当预测发现当前光标在菜单的左侧时,会将菜单右侧的部分内容切掉,将当前坐标作为菜单的右侧.此时左上角的坐标为光标减去菜单宽度的值。完整的代码是:constareaEl=document.querySelector('.page-view')constmask=document.querySelector('.contextmenu-mask')constcontentEl=document.querySelector('.contextmenu-content')/****@param{number}x待设置菜单左上角坐标x*@param{number}y左上角y*@param{number}w菜单宽度*@param{number}h菜单的高度*@returns{x,y}调整后的坐标*/constadjustPos=(x,y,w,h)=>{constPADDING_RIGHT=6//右边留一些空间,防止直接对齐,即不好constPADDING_BOTTOM=6//底部也要留空constvw=document.documentElement.clientWidthconstvh=document.documentElement.clientHeightif(x+w>vw-PADDING_RIGHT)x-=wif(y+h>vh-PADDING_BOTTOM)y-=hreturn{x,y}}constonContextMenu=e=>{e.preventDefault()constrect=contentEl.getBoundingClientRect()//console.log(rect)const{x,y}=adjustPos(e.clientX,e.clientY,rect.width,rect.height)showContextMenu(x,y)}//阻止指定元素区域下的菜单事件El.addEventListener('contextmenu',onContextMenu,false)隐藏右键菜单不使用常规display:none;,而是使用left和top设置一个大的值。这是因为我要实现的是自适应宽高的右键菜单。为此,需要动态获取菜单的宽高,需要使用Element.getBoundingClientRect()方法,而该方法要求元素在DOM树中并且是可见元素,以便获取宽度和高度,否则只能获取两个0。如果你要实现的菜单宽度是手动硬编码的,高度是根据菜单项的个数计算的,那么隐藏菜单最好的方案就是display:none。隐藏菜单点击菜单项然后点击遮罩层隐藏菜单和遮罩。并点击菜单项,执行相应的命令consthideContextMenu=()=>{mask.style.display='none'contentEl.style.top='99999px'contentEl.style.left='99999px'}//点击onthemask,hidemask.addEventListener('mousedown',()=>{hideContextMenu()},false)//点击菜单,隐藏contentEl.addEventListener('click',(e)=>{console.log('click:',e.target.textContent)//执行菜单项对应的命令hideContextMenu()},false)其他需要考虑的事项窗口缩小:窗口缩小会导致右下方菜单跳出窗口区域,是否考虑监听窗口事件。右键菜单的逻辑:将右键菜单重定位到这个位置,或者相当于点击左键,或者不做处理,浏览器原生的右键菜单会弹出,你需要根据自己的需要来选择。最后实现自定义菜单的逻辑并不复杂,就是修改contextmenu事件的行为来显示或者隐藏自己写的div。但是有一些细节需要处理,才能写出一个优秀的没有bug的右键菜单。