实现原理

其实就是在鼠标点击位置弹出一个绝对位置的div层,如果点击位置靠右或靠下,防止菜单超出屏幕,会向上或向左进行一定的位移, 因为弹出菜单的使用场景会比较多,考虑封装成组件方便以后调用,减少重复开发。

效果如下

涉及到的样式类

1、固定定位,也叫绝对定位,因为要在光标的指定位置进行显示

.position-fixed { 
        position: fixed; 
} 

2、popup-menu菜单样式,z-index数字要大点,这样可以显示在最顶层,popup-menu-item为菜单项样式,popup-menu-item:hover为光标经过菜单项变色样式

.popup-menu {

        width: 150px;
        padding: 10px;
        border-radius: 10px;
        background-color:#fff;
        border-style: solid;
        border-width: 1px;
        border-color: rgba(0, 0, 0, .2);
        z-index: 999;    
}

.popup-menu-item {
        height: 30px;
}

.popup-menu-item:hover {
        border-radius: 5px;
        background: rgba(0, 0, 0, .1);
}
子组件代码
<template>
    <div v-if="showMenu" class="position-fixed popup-menu flex flex-col" :style="{top:clientY, left:clientX}">
        <div v-for="item in menuData"
        class="popup-menu-item pointer flex flex-center-cz padding-left-m" :data-id="item.id"
        @click="itemClick" >{{item.name}}</div>
    </div>
</template>
   
  <script>
  
  export default {
      name: 'PopupMenu',    
      props: {
          menuData:{},
      },

      data() {
          return {
            showMenu: false,
            clientX:'',
            clientY:'',
          }
      },
  
      methods: {

        show(event) {
            console.log("父组件传过来的event", event);
            let x = event.x; //鼠标点击的x坐标
            let y = event.y; //鼠标点击的y坐标
            let menuWidth = 150;//菜单宽度,这个与.popup-menu样式对应的宽度一致
            let menuHeight = this.menuData.length * 30 + 20; //菜单高度,根据菜单项自动计算的高度

            this.clientX = (parseFloat(x) - 10) + "px";
            this.clientY = (parseFloat(y) + 10) + "px";

            let windowWidth = document.documentElement.clientWidth; // 实时屏幕宽度
            let windowHeight = document.documentElement.clientHeight; // 实时屏幕高度

            if (parseFloat(x) + menuWidth > windowWidth) {
                this.clientX = (parseFloat(windowWidth) - menuWidth - 50)  + "px";
            }
            if (parseFloat(y) + menuHeight > windowHeight) {
                this.clientY = (parseFloat(windowHeight) -menuHeight - 50)  + "px";
            }
            this.showMenu = true;
            event.stopPropagation();//阻止事件冒泡
            document.addEventListener("click", this.closeMenu, false);// 添加关闭事件
        },         
        closeMenu() {
            console.log("销毁监听事件。");
            document.removeEventListener("click", this.closeMenu, false);//销毁关闭事件
            this.showMenu = false;
        },
        itemClick(event) {
            let id = event.target.getAttribute("data-id");//获取菜单项id
            this.$emit('menuClick', id);//传参调用父组件事件,让父组件知道是点击到哪个菜单项
        }

      }
  }
  </script>
  
  <style>
    .popup-menu {

        width: 150px;
        padding: 10px;
        border-radius: 10px;
        background-color:#fff;
        border-style: solid;
        border-width: 1px;
        border-color: rgba(0, 0, 0, .2);
        z-index: 999;    
    }

    .popup-menu-item {
        height: 30px;
    }
    .popup-menu-item:hover {
        border-radius: 5px;
        background: rgba(0, 0, 0, .1);
    }

  </style>
父组件代码
<template>
  <div class="body">
    <div class="pnl-top" >
        <div class="pointer cannotselect margin-top-l margin-left-l btn-blue-full" @click="popup">正常弹出</div>
        <div class="pointer cannotselect margin-top-l margin-right-l btn-blue-full" @click="popup">向左弹出</div>
    </div>
    <div class="pnl-bottom" >
        <div class="pointer cannotselect margin-top-l margin-left-l btn-blue-full" @click="popup">向上弹出</div>
        <div class="pointer cannotselect margin-top-l margin-right-l btn-blue-full" @click="popup">左上弹出</div>
    </div>
    <popup-menu ref="child" :menuData="menuData"  @menuClick="menuClick(arguments)" >
    </popup-menu>

  </div>
</template>

<script>
/*
     名称:vue自定义弹出菜单
     功能:        
     作者:唐赢   
     时间:2023-1-17
*/
import PopupMenu from '@/components/PopupMenu'


export default {
  name: 'Main',
  components: {
    PopupMenu
  },
  data () {
    return {
      menuData:[{id:'rename',name:'重命名'},{id:'remove',name:'移动'},{id:'delete',name:'删除'}],   //菜单数据  
    }
  },
  methods: {

    popup(event) {
        this.$refs.child.show(event);//调用子组件的show方法,用于显示弹出式菜单,引用组件时,需要 ref="child"
    },
    menuClick(args) {
       // 子组件的方法 @menuClick="menuClick(arguments)" 注意menuClick()里面一定要填写arguments,用于接受子组件的传参
       let id = args[0];
       alert("您点击了:" + id);

    }
  },

  
}
</script>

<style scoped>
.body {
  display: relative;
  justify-content: center;
  margin-top: 73px;
  width: 100%;    
}

.pnl-top {
  position: absolute;
  display: flex;
  justify-content: space-between;
  top: 70;
  left: 0;
  right: 0;
  height: 60px;
}

.pnl-bottom {
  position: absolute;
  display: flex;
  justify-content: space-between;
  bottom: 0;
  left: 0;
  right: 0;
  height: 60px;
}
</style>
演示及下载地址

演示地址:https://lusun.com/v/UCyEe49pZ3f

源码地址:https://download.csdn.net/download/gdgztt/87397842

Logo

前往低代码交流专区

更多推荐