Vue 实现动态右键菜单,位置动态

要求:鼠标右键出现右键菜单,并随鼠标点击的位置,菜单位置进行变化

效果图:

代码如下:

index.vue

<template>
  <div>
    <div class="father" @contextmenu.prevent="rightClick($event)"></div>
    <context-menu
      v-model="showContextMenu"
      :position="showPosition"
      @on-click-menu="clickContextMenu"
    >
    </context-menu>
  </div>
</template>
<script>
import contextMenu from "./contextMenu.vue";
export default {
  name: "father",
  components: { contextMenu },
  data() {
    return {
      showContextMenu: false,
      showPosition: {},
    };
  },
  methods: {
    rightClick(event) {
      this.showPosition = {
        y: event.y,
        x: event.x,
      };
      this.showContextMenu = true;
    },
    clickContextMenu(value) {
      console.log(value);
    },
  },
};
</script>
<style>
.father {
  width: 200px;
  height: 200px;
  border: 1px solid;
  background: #fff;
}
</style>

contextMenu.vue

<template>
  <div
    class="theContextMenu"
    id="theContextMenu"
    v-clickoutside="closePicker"
    :style="contextMenuStyle"
    v-show="visible"
  >
    <div v-for="(list, index) in menuList" :key="index">
      <ul :class="index < menuList.length - 1 ? 'splitLine' : ''">
        <li
          v-for="item in list"
          :key="item.value"
          @click="clickMenu(item.value)"
        >
          {{ item.label }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
const clickoutside = {
  // 初始化指令
  bind(el, binding, vnode) {
    function documentHandler(e) {
      // 这里判断点击的元素是否是本身,是本身,则返回
      if (el.contains(e.target)) {
        return false;
      }
      // 判断指令中是否绑定了函数
      if (binding.expression) {
        // 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
        binding.value(e);
      }
    }
    // 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
    el.__vueClickOutside__ = documentHandler;
    document.addEventListener("click", documentHandler);
  },
  unbind(el, binding) {
    // 解除事件监听
    document.removeEventListener("click", el.__vueClickOutside__);
    delete el.__vueClickOutside__;
  },
};
export default {
  name: "context-menu",
  model: {
    prop: "visible",
    event: "change",
  },
  props: {
    position: {
      type: Object,
      default: () => {
        return {
          y: 0,
          x: 0,
        };
      },
    },
    visible: {
      type: Boolean,
      default: false,
    },
  },
  directives: { clickoutside },
  data() {
    return {
      menuList: [
        [{ label: "menu1", value: "menu1" }],
        [
          { label: "menu2-1", value: "menu21" },
          { label: "menu2-2", value: "menu22" },
        ],
        [{ label: "menu3", value: "menu3" }],
      ],
      width: 0,
      height: 0,
      contextMenuStyle: {},
    };
  },
  watch: {
    position() {
      this.changePosition();
    },
    visible(value) {
      if (value && !this.width) {
        this.$nextTick(() => {
          const ele = document.getElementById("theContextMenu");
          this.width = ele.offsetWidth;
          this.height = ele.offsetHeight;
          this.changePosition();
        });
      }
    },
  },
  methods: {
    changePosition() {
      if (!this.width) {
        return;
      }
      let left = this.position.x;
      let top = this.position.y;

      if (left + this.width > document.documentElement.clientWidth) {
        left = left - this.width;
      }
      if (top + this.height > document.documentElement.clientHeight) {
        top = top - this.height;
      }
      this.contextMenuStyle = {
        top: top + "px",
        left: left + "px",
      };
    },
    clickMenu(value) {
      this.$emit("on-click-menu", value);
      this.closePicker();
    },
    closePicker() {
      this.$emit("change", false);
    },
  },
};
</script>

<style>
.theContextMenu {
  position: fixed;
  border: 1px solid #ddd;
  border-radius: 0.3rem;
  background: #fff;
  z-index: 1;
}
ul {
  padding: 5px 0;
  list-style: none;
}
li {
  padding: 5px 10px;
  cursor: pointer;
}
li:hover {
  background: #f3f3f3;
}
.splitLine {
  border-bottom: 1px solid #e8eaec;
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值