取消原生右键事件
在 main.ts 函数中取消浏览器默认右键菜单:
window.oncontextmenu = () => {
return false;
};
组件模板
做一个不同区域右键点击之后不同菜单项的组件,创建组件模板:
<!-- ContextMenu -->
<div ref="menu" class="l-menu">
<slot />
<Teleport to="#l-menu-container">
<div
ref="panel"
:id="'l-menu__panel__' + milliseconds"
:style="{ left: x + 'px', top: y + 'px' }">
<div ref="head" class="l-menu__head">
<div class="l-menu__title">
<slot name="title" />
</div>
<div @click="panel.style.display = 'none'">
<i-ep-close />
</div>
</div>
<div class="l-menu__main">
<slot name="content" />
</div>
</div>
</Teleport>
</div>
模板中使用了 Teleport
,需要把这一块传递到页面中 l-menu-container
元素中去,这个元素在 body 下,意思是我的右键菜单组件呼出的面板不受父组件的样式影响,包括移动的范围限制、背景颜色等,这些继承父元素样式都需要避免。
组件 setup
需要获取组件模板的一些引用:
// ContextMenu setup
const menu = ref<HTMLElement>();
const head = ref<HTMLElement>();
const panel = ref<HTMLElement>();
const milliseconds = new Date().getMilliseconds();
const { x, y } = useDraggable(head);
onMounted(() => {
menu.value.onmouseup = e => {
if (e.button == 2) {
const container = document.querySelector("#l-menu-container");
const menuId = container.getAttribute("menu-id");
menuId && (document.getElementById(`l-menu__panel__${menuId}`).style.display = "none");
container.setAttribute("menu-id", `${milliseconds}`);
panel.value.style.left = `${e.clientX}px`;
panel.value.style.top = `${e.clientY}px`;
panel.value.style.display = "block";
}
};
});
鼠标按下放开事件,判断鼠标是左键还是右键,button 为 2 代表右键,右键点击之后需要移除上一个开启的面板,在 body 下的 #l-menu-container
元素上添加一个记录上一次面板的 id。
实现右键出来之后的菜单面板自由地在窗口中移动,直接借助 VueUse useDraggable
函数:
const { x, y } = useDraggable(head);
得到鼠标移动的 x 和 y 值,通过绑定 style 对 left 和 top 进行设置,就可以实现自由移动。
使用组件
该组件有三个插槽,一个默认插槽,两个具名插槽。具名 content 插槽是菜单内容,默认插槽是能呼出右键菜单面板的区域,哪个区域能呼出就在外面套一层 ContextMenu 组件:
<ContextMenu>
<div class="item">
hello
</div>
<template #title>样式设置</template>
<template #content>
<BoxSetting />
</template>
</ContextMenu>