侧边栏
实现一个Vue右键菜单指令
最近在实现可视化页面编辑器的时候,遇见了需要实现自定义右键菜单的场景,因此本文主要整理了如何在Vue项目中通过自定义指令封装一个声明式的右键菜单的小工具。
整个项目已经发布到 npm:vue-contextmenu,对应源码位于github。
右键菜单组件
html
<template>
<div v-show="visible" class="context-menu">
<div
v-for="(item, index) in list"
:key="index"
class="context-menu-item"
@click.stop="clickHandler(item)"
>
{{ item.text }}
</div>
</div>
</template>
<script>
export default {
name: "ContextMenu",
props: {
visible: {
type: Boolean,
default: false,
},
list: {
type: Array,
default: () => {
return [];
},
},
},
methods: {
close() {
this.$emit("hide");
},
clickHandler(item) {
if (typeof item.onClick === "function") {
item.onClick();
}
this.close();
},
},
};
</script>
<style scoped lang="scss">
.context-menu {
position: absolute;
z-index: 999;
background-color: #ecf0f1;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 3px 6px 0 rgba(51, 51, 51, 0.2);
&-item {
line-height: 40px;
padding: 0 10px;
font-size: 14px;
&:hover {
background-color: #007aff;
color: #fff;
cursor: pointer;
}
}
}
</style>
其中单个菜单项数据结构类似于
js
{
text: '菜单项目1',
onClick: () => {
console.log('todo')
}
}
考虑到还有 2 级或多级菜单,可以扩展一个诸如subMenu
的字段保存子菜单。
contextmenu 指令
单例
在绝大部分场景下,右键菜单应该是一个单例,因此可以通过一个 Vue 实例利用手动$mount 挂载到页面上,然后控制展示和隐藏该节点来实现,无须实例化多个菜单
为了避免在模块中直接引入外部 Vue,我们将其封装成构造参数传入,同时将菜单组件也作为第二个参数传入,方便后续替换自定义菜单组件
js
export default function init(Vue, MenuComponent) {
function newInstance() {
const Instance = new Vue({
data() {
return {
list: [],
style: {},
visible: false,
};
},
mounted() {
document.addEventListener("click", this.hide);
document.addEventListener("contextmenu", this.hide);
},
beforeDestroy() {
document.removeEventListener("click", this.hide);
document.removeEventListener("contextmenu", this.hide);
},
methods: {
show(list, style) {
this.list = list;
this.style = style;
this.visible = true;
},
hide() {
this.visible = false;
},
},
render(h) {
return h(MenuComponent, {
style: this.style,
props: {
visible: this.visible,
list: this.list,
},
on: {
hide: this.hide,
},
});
},
});
const el = Instance.$mount();
document.body.appendChild(el.$el);
return el;
}
let instance;
return function getInstance() {
if (!instance) {
instance = newInstance();
}
return instance;
};
}
自定义指令
为了方便在不同的元素上展示不同的菜单,用指令来封装菜单的显示,比直接引入菜单组件在模板中展示更加灵活。因此采用自定义指令的方式来封装整个插件。
在指令的bind
钩子中,主要监听 dom 节点的 contextmenu 事件,然后将菜单组件展示在指定位置。
js
const contextMenu = {
bind(el, binding) {
const instance = getInstance();
el.addEventListener("contextmenu", function (e) {
const { menuList, onShow } = binding.value;
if (typeof onShow === "function") {
onShow();
}
// 在指定位置展示
const oX = e.clientX;
const oY = e.clientY;
instance.show(menuList, {
left: oX + "px",
top: oY + "px",
});
e.preventDefault();
e.stopPropagation();
});
},
};
最后向外暴露一个插件接口
js
export default {
install(Vue, {name = 'contextmenu', menuComponent = Menu} = {}) {
const getInstance = init(Vue, menuComponent)
const contextMenu = {...}
Vue.directive(name, contextMenu)
}
}
这里暴露了两个配置参数方便扩展
name
,指令名称menuComponent
,自定义菜单组件
在组件中使用
全局注册插件后,就可以在组件中通过指令使用了
html
<template>
<div id="app">
<button v-contextmenu="{menuList, onShow}">右键菜单</button>
</div>
</template>
<script>
export default {
name: 'App',
computed: {
menuList() {
return [
{
text: '菜单1',
onClick: () => {
console.log(1)
}
},
{
text: '菜单2', onClick: () => {
console.log(2)
}
}
]
},
},
methods:{
onShow(){
// init
}
}
}
</script>
支持binding传值包括
menuList
,菜单列表,格式如下text
菜单名称onClick
点击事件
onShow
,钩子函数,在菜单展示时触发
这样就实现了通过声明式的指令配置不同地方的右键菜单,并执行对应逻辑。
小结
本文介绍了使用自定义指令实现一个易用的右键菜单,其实现思路比较简单
- 监听右键菜单事件
oncontextmenu
,获取鼠标点击位置 - 在对应位置展示菜单组件
- 渲染菜单
相关代码已经放在github上了,后续有时间会支持多级菜单等新特性。
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。