取消原生右键事件

在 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>

实现效果

做一个 Vue 右键菜单组件

发表回复