vue3 右键指令实现

920 阅读1分钟

效果展示

consssss.gif

具体实现

  • 创建指令并绑定到vue实例上,给元素添加右键事件

    export const ContextmenuDirective: Directive = {
      mounted: (el: HTMLElement, binding) => {
        el[CTX_CONTEXTMENU_HANDLER] = (event: MouseEvent) => contextmenuListener(el, event, binding);
        el.addEventListener('contextmenu', el[CTX_CONTEXTMENU_HANDLER]);
      },
      unmounted: (el: HTMLElement, binding) => {
        if (el && el[CTX_CONTEXTMENU_HANDLER]) {
          el.removeEventListener('contextmenu', el[CTX_CONTEXTMENU_HANDLER]);
          delete el[CTX_CONTEXTMENU_HANDLER];
        }
      },
    };
    
  • 实现右键事件,把右键菜单添加到body上

    const contextmenuListener = (el: HTMLElement, event: MouseEvent, binding: DirectiveBinding) => {
      event.stopPropagation();
      event.preventDefault();
      const menus = binding.value(el);
      if (!menus) return;
      let container: HTMLDivElement | null = null;
    ​
      // 移除右键菜单并取消相关的事件监听
      const removeContextmenu = () => {
        if (container) {
          document.body.removeChild(container);
          container = null;
        }
        el.classList.remove('contextmenu-active');
        document.body.removeEventListener('scroll', removeContextmenu);
        window.removeEventListener('resize', removeContextmenu);
      };
    ​
      // 创建自定义菜单
      const options = {
        axis: { x: event.x, y: event.y },
        el,
        menus,
        removeContextmenu,
      };
      container = document.createElement('div');
      const vm = createVNode(ContextmenuComponent, options, null);
      render(vm, container);
      document.body.appendChild(container);
    ​
      // 为目标节点添加菜单激活状态的className
      el.classList.add('contextmenu-active');
    ​
      // 页面变化时移除菜单
      document.body.addEventListener('scroll', removeContextmenu);
      window.addEventListener('resize', removeContextmenu);
    };
    
  • 右键内容组件实现

    <template>
      <div
        class="mask"
        @contextmenu.prevent="removeContextmenu()"
        @mousedown="removeContextmenu()"
      ></div>
      <div
        class="contextmenu"
        :style="{
          left: style.left + 'px',
          top: style.top + 'px',
        }"
        @contextmenu.prevent
      >
        <MenuContent :menus="menus" :handleClickMenuItem="handleClickMenuItem" />
      </div>
    </template>
    

    添加mask蒙版的作用是,当鼠标在右键菜单外点击时,当前右键组件删除。

    // 计算内容组件出现的位置,并设置相应样式
    const style = computed(() => {
        const MENU_WIDTH = 170;
        const MENU_HEIGHT = 30;
        const DIVIDER_HEIGHT = 11;
        const PADDING = 5;
    ​
        const { x, y } = props.axis;
        const menuCount = props.menus.filter((menu) => !(menu.divider || menu.hide)).length;
        const dividerCount = props.menus.filter((menu) => menu.divider).length;
    ​
        const menuWidth = MENU_WIDTH;
        const menuHeight = menuCount * MENU_HEIGHT + dividerCount * DIVIDER_HEIGHT + PADDING * 2;
    ​
        const screenWidth = document.body.clientWidth;
        const screenHeight = document.body.clientHeight;
    ​
        return {
          left: screenWidth <= x + menuWidth ? x - menuWidth : x,
          top: screenHeight <= y + menuHeight ? y - menuHeight : y,
        };
      });
    // 处理点击事件
      const handleClickMenuItem = (item: ContextmenuItem) => {
        if (item.disable) return;
        if (item.children && !item.handler) return;
        if (item.handler) item.handler(props.el);
        props.removeContextmenu();
      };
    

使用

<a-button v-contextmenu="contextmenu">右键菜单</a-button>
const contextmenu = () => {
    return [
      {
        text: '粘贴',
        subText: 'Ctrl + V',
        handler: () => console.log('粘贴'),
      },
      {
        text: '全选',
        subText: 'Ctrl + A',
        handler: () => console.log('全选'),
      },
    ];
  };

完整代码 如果觉得文章对你有帮助,欢迎一键三连