JS使用iView的Dropdown实现一个右键菜单

作者:百宝袋

时间:2026-10-23

浏览:639

本文介绍如何在Vue项目中利用iView的Dropdown组件实现自定义右键菜单。通过深入分析源码,发现slot外层div的ref属性定位机制,进而通过动态创建fixed定位元素并替换reference节点的方式,成功解决菜单定位偏移问题,最终封装为可复用的右键菜单组件。

文章目录

前言与背景

前言

近期在基于iView框架进行项目开发时,遇到了需要自定义右键菜单的需求。查阅官方文档后,注意到Dropdown组件具备相关功能,因此尝试将其改造为右键菜单组件。

阅读者需具备一定的iView使用基础。

初步尝试与问题发现

尝试

Dropdown组件的基本用法如下所示。


<template>
 <Dropdown>
  <a href="javascript:void(0)" rel="external nofollow" >
   下拉菜单
   <Icon type="ios-arrow-down"></Icon>
  </a>
  <DropdownMenu slot="list">
   <DropdownItem>驴打滚</DropdownItem>
   <DropdownItem>炸酱面</DropdownItem>
   <DropdownItem disabled>豆汁儿</DropdownItem>
   <DropdownItem>冰糖葫芦</DropdownItem>
   <DropdownItem divided>北京烤鸭</DropdownItem>
  </DropdownMenu>
 </Dropdown>
</template>
<script>
 export default {

 }
</script>

注意到组件提供了一个用于触发内容的slot插槽,允许自定义插入元素。思路是:将slot内的内容设置为position: fixed属性,并在触发右键时实时获取鼠标坐标并赋值给该元素,从而实现菜单跟随鼠标位置显示。经过初步调试,这一思路看似可行。


<template>
 <Dropdown
  transfer
  placement="right-start"
  trigger="custom"
  :visible="currentVisible"
  @on-clickoutside="handleCancel"
 >
  <div :style="locatorStyle"></div>
  <DropdownMenu slot="list">
   <DropdownItem>驴打滚</DropdownItem>
   <DropdownItem>炸酱面</DropdownItem>
   <DropdownItem disabled>豆汁儿</DropdownItem>
   <DropdownItem>冰糖葫芦</DropdownItem>
   <DropdownItem divided>北京烤鸭</DropdownItem>
  </DropdownMenu>
 </Dropdown>
</template>
<script>
export default {
 data () {
  return {
   posX: 0,
   posY: 0,
   currentVisible: false
  }
 },
 computed: {
  locatorStyle () {
   return {
    position: 'fixed',
    left: `${this.posX}px`,
    top: `${this.posY}px`
   }
  }
 },
 methods: {
  handleContextmenu ({ button, clientX, clientY }) {
   if (button === 2) {
    if (this.posX !== clientX) this.posX = clientX
    if (this.posY !== clientY) this.posY = clientY
    this.currentVisible = true
   }
  },
  handleCancel () {
   this.currentVisible = false
  }
 },
 mounted () {
  document.addEventListener('contextmenu', this.handleContextmenu, true)
  document.addEventListener('mouseup', this.handleContextmenu, true)
 },
 destroyed () {
  document.removeEventListener('contextmenu', this.handleContextmenu, true)
  document.removeEventListener('mouseup', this.handleContextmenu, true)
 }
}
</script>

然而实际测试中发现,无论点击何处,菜单始终固定在页面右上角显示。

前言与背景

前言与背景

虽然slot内元素的位置确实发生了改变,但菜单整体的渲染位置并未随之更新。

经过长时间排查未能解决,最终决定深入查看Dropdown组件的源码以探究原因。


<template>
  <div
    :class="[prefixCls]"
    v-click-outside="onClickoutside"
    @mouseenter="handleMouseenter"
    @mouseleave="handleMouseleave">
    <!-- 注意此处 -->
    <div :class="relClasses" ref="reference" @click="handleClick" @contextmenu.prevent="handleRightClick"><slot></slot></div>
    <transition name="transition-drop">
      <Drop
        :class="dropdownCls"
        v-show="currentVisible"
        :placement="placement"
        ref="drop"
        @mouseenter.native="handleMouseenter"
        @mouseleave.native="handleMouseleave"
        :data-transfer="transfer"
        :transfer="transfer"
        v-transfer-dom><slot name="list"></slot></Drop>
    </transition>
  </div>
</template>
<script>
// 以下省略
</script>

源码显示,slot内容被包裹在一个外层div中,而Dropdown组件的定位逻辑依赖于这个外层div。因此,无论slot内部内容如何变化位置,初始化后该外层div的位置固定不变,导致菜单无法跟随slot内容移动(这也可能与position: fixed的定位特性有关)。

源码分析与解决方案

调整方案

观察到slot外层div存在一个ref="reference"属性。

由此产生新想法:是否可以直接通过Dropdown组件的refs接口,将整个外层div替换掉?基于此思路继续调试并进行了代码改造。


<template>
 <Dropdown
  transfer
  placement="right-start"
  trigger="custom"
  ref="contextMenu"
  :visible="currentVisible"
  @on-clickoutside="handleCancel"
 >
  <DropdownMenu slot="list">
   <DropdownItem>驴打滚</DropdownItem>
   <DropdownItem>炸酱面</DropdownItem>
   <DropdownItem disabled>豆汁儿</DropdownItem>
   <DropdownItem>冰糖葫芦</DropdownItem>
   <DropdownItem divided>北京烤鸭</DropdownItem>
  </DropdownMenu>
 </Dropdown>
</template>
<script>
export default {
 data () {
  return {
   posX: 0,
   posY: 0,
   currentVisible: false,
   locator: null
  }
 },
 methods: {
  createLocator () {
   // 获取Dropdown
   const contextmenu = this.$refs.contextMenu
   // 创建locator
   const locator = document.createElement('div')
   locator.style.cssText = `position:fixed;left:${this.posX}px;top:${this.posY}px`
   document.body.appendChild(locator)
   // 将locator绑定到Dropdown的reference上
   contextmenu.$refs.reference = locator
   this.locator = locator
  },
  removeLocator () {
   if (this.locator) document.body.removeChild(this.locator)
   this.locator = null
  },
  handleContextmenu ({ button, clientX, clientY }) {
   if (button === 2) {
    if (this.posX !== clientX) this.posX = clientX
    if (this.posY !== clientY) this.posY = clientY
    if (this.trigger !== 'custom') {
     this.createLocator()
     this.currentVisible = true
    }
   }
  },
  handleCancel () {
   this.currentVisible = false
   this.removeLocator()
  }
 },
 mounted () {
  document.addEventListener('contextmenu', this.handleContextmenu, true)
  document.addEventListener('mouseup', this.handleContextmenu, true)
 },
 destroyed () {
  document.removeEventListener('contextmenu', this.handleContextmenu, true)
  document.removeEventListener('mouseup', this.handleContextmenu, true)
 }
}
</script>

具体实现为:根据鼠标点击位置动态创建一个position: fixed样式的div元素。通过给Dropdown组件添加ref属性获取组件实例,进而利用$refs访问到该实例,并将新创建的div赋值给其内部的reference节点。

经过上述修改,Dropdown菜单现在能够准确地出现在鼠标所在位置。

前言与背景

最后补充完善相关的点击回调方法,即可构成一个功能完整的右键菜单组件。

为了提升组件的复用性,还需进一步提取通用逻辑代码,并补充常用的API接口。具体实现代码可参考相关代码仓库。

总结

总结

以上介绍了利用JS和iView的Dropdown组件实现自定义右键菜单的方法,希望能对读者有所帮助。如有任何疑问,欢迎留言交流,作者会及时回复。感谢大家对本站的支持! 如果本文对您有帮助,欢迎转载,请注明出处,谢谢!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章