JS使用iView的Dropdown实现一个右键菜单
本文介绍如何在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组件实现自定义右键菜单的方法,希望能对读者有所帮助。如有任何疑问,欢迎留言交流,作者会及时回复。感谢大家对本站的支持! 如果本文对您有帮助,欢迎转载,请注明出处,谢谢!
定制 Quill.js 主题样式:修改默认 CSS 与添加自定义类名
2026-11-04 11:21:35
混淆JS代码怎么在线反混淆并格式化阅读
2026-11-03 21:46:39
JS在线编辑器代码报错怎么调试?控制台使用技巧
2026-11-03 21:24:56
从 marked.js 旧版本升级到最新版:API 变更与迁移指南
2026-11-03 17:38:17
JS富文本编辑器Quill与代码编辑器CodeMirror的区别及应用场景
2026-11-03 16:52:09
