实现一个Vue右键菜单指令

更新日期: 2021-07-23阅读: 1.6k标签: 指令

最近在实现可视化页面编辑器的时候,遇见了需要实现自定义右键菜单的场景,因此本文主要整理了如何在vue项目中通过自定义指令封装一个声明式的右键菜单的小工具

整个项目已经发布到 npmvue-contextmenu,对应源码位于github


1. 右键菜单组件

<template>
    <div v-show="visible" class="context-menu">
        <div
            v-for="(item, index) in list"
            :key="index"
            class="context-menu-item"
            @click.stop="clickHandler(item)"
        >
            {{ item.text }}
        </div>
    </div>
</template>

<script>
    export default {
        name: "ContextMenu",
        props: {
            visible: {
                type: Boolean,
                default: false,
            },
            list: {
                type: Array,
                default: () => {
                    return [];
                },
            },
        },
        methods: {
            close() {
                this.$emit("hide");
            },
            clickHandler(item) {
                if (typeof item.onClick === "function") {
                    item.onClick();
                }
                this.close();
            },
        },
    };
</script>

<style scoped lang="scss">
    .context-menu {
        position: absolute;
        z-index: 999;
        background-color: #ecf0f1;
        border-radius: 10px;
        overflow: hidden;
        box-shadow: 0 3px 6px 0 rgba(51, 51, 51, 0.2);

        &-item {
            line-height: 40px;
            padding: 0 10px;
            font-size: 14px;
            &:hover {
                background-color: #007aff;
                color: #fff;
                cursor: pointer;
            }
        }
    }
</style>

其中单个菜单项数据结构类似于

{
  text: '菜单项目1',
  onClick: () => {
    console.log('todo')
  }
}

考虑到还有 2 级或多级菜单,可以扩展一个诸如subMenu的字段保存子菜单。

2. contextmenu 指令

2.1. 单例

在绝大部分场景下,右键菜单应该是一个单例,因此可以通过一个 Vue 实例利用手动$mount 挂载到页面上,然后控制展示和隐藏该节点来实现,无须实例化多个菜单

为了避免在模块中直接引入外部 Vue,我们将其封装成构造参数传入,同时将菜单组件也作为第二个参数传入,方便后续替换自定义菜单组件

export default function init(Vue, MenuComponent) {
    function newInstance() {
        const Instance = new Vue({
            data() {
                return {
                    list: [],
                    style: {},
                    visible: false,
                };
            },
            mounted() {
                document.addEventListener("click", this.hide);
                document.addEventListener("contextmenu", this.hide);
            },
            beforeDestroy() {
                document.removeEventListener("click", this.hide);
                document.removeEventListener("contextmenu", this.hide);
            },
            methods: {
                show(list, style) {
                    this.list = list;
                    this.style = style;
                    this.visible = true;
                },
                hide() {
                    this.visible = false;
                },
            },
            render(h) {
                return h(MenuComponent, {
                    style: this.style,
                    props: {
                        visible: this.visible,
                        list: this.list,
                    },
                    on: {
                        hide: this.hide,
                    },
                });
            },
        });
        const el = Instance.$mount();
        document.body.appendChild(el.$el);
        return el;
    }

    let instance;

    return function getInstance() {
        if (!instance) {
            instance = newInstance();
        }
        return instance;
    };
}

2.2. 自定义指令

为了方便在不同的元素上展示不同的菜单,用指令来封装菜单的显示,比直接引入菜单组件在模板中展示更加灵活。因此采用自定义指令的方式来封装整个插件。

在指令的bind钩子中,主要监听 dom 节点的 contextmenu 事件,然后将菜单组件展示在指定位置。

const contextMenu = {
    bind(el, binding) {
        const instance = getInstance();

        el.addEventListener("contextmenu", function (e) {
            const { menuList, onShow } = binding.value;
            if (typeof onShow === "function") {
                onShow();
            }

            // 在指定位置展示
            const oX = e.clientX;
            const oY = e.clientY;
            instance.show(menuList, {
                left: oX + "px",
                top: oY + "px",
            });
            e.preventDefault();
            e.stopPropagation();
        });
    },
};

最后向外暴露一个插件接口

export default {
    install(Vue, {name = 'contextmenu', menuComponent = Menu} = {}) {
        const getInstance = init(Vue, menuComponent)
        const contextMenu = {...}
        Vue.directive(name, contextMenu)
    }
}

这里暴露了两个配置参数方便扩展

  • name,指令名称
  • menuComponent,自定义菜单组件

3. 在组件中使用

全局注册插件后,就可以在组件中通过指令使用了

<template>
  <div id="app">
    <button v-contextmenu="{menuList, onShow}">右键菜单</button>
  </div>
</template>

<script>

export default {
  name: 'App',
  computed: {
    menuList() {
      return [
        {
          text: '菜单1',
          onClick: () => {
            console.log(1)
          }
        },
        {
          text: '菜单2', onClick: () => {
            console.log(2)
          }
        }
      ]
    },
  },
  methods:{
    onShow(){
      // init
    }
  }
}
</script>

支持binding传值包括

  • menuList,菜单列表,格式如下
    • text 菜单名称
    • onClick 点击事件
  • onShow,钩子函数,在菜单展示时触发

这样就实现了通过声明式的指令配置不同地方的右键菜单,并执行对应逻辑。

4. 小结

本文介绍了使用自定义指令实现一个易用的右键菜单,其实现思路比较简单

  • 监听右键菜单事件oncontextmenu,获取鼠标点击位置
  • 在对应位置展示菜单组件
  • 渲染菜单

相关代码已经放在github上了,后续有时间会支持多级菜单等新特性。

原文:https://www.shymean.com/article/实现一个Vue右键菜单指令


链接: https://www.fly63.com/article/detial/10566

指令式编程 VS 声明式编程

指令式编程:告诉机器该如何做,并得到自己想要的结果。声明式编程:告诉机器您想得到什么,让机器自己计算该如何做。

vue内置指令大全_整理常用的Vue内置指令

整理vue常用的内置指令:v-bind指令、v-text指令、v- html指令、v-show指令、v-if指令、v-else 指令、v-else-if 指令、v-for 指令、v-on 指令、v-model 指令、v-once 指令、v-cloak指令、v-pre指令

如何在Vue里建立长按指令

本文将解释如何通过按下(或按住)按钮来执行功能和删除输入。首先,我将解释如何在VanillaJS中实现这一目标。然后,为它创建一个Vue指令。那么,让我们开始吧。

深入解析Vue.directive 自定义指令

全局API并不在构造器里,而是先声明全局变量或者直接在Vue上定义一些新功能,Vue内置了一些全局API,比如我们今天要学习的指令Vue.directive。说的简单些就是,在构造器外部用Vue提供给我们的API函数来定义新的功能。

VUE指令大全

v-text主要用来更新textContent,可以等同于JS的text属性。v-html双大括号的方式会将数据解释为纯文本,而非HTML。为了输出真正的HTML,可以用v-html指令。它等同于JS的innerHtml属性。

Vue中的全部指令

可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:动态参数预期会求出一个字符串,异常情况下值为 null。这个特殊的 null 值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。

vue v-for 使用问题Error in render

今天使用v-for指令的时候遇到一个错误:[Vue warn]: Error in render: \\\"TypeError: Cannot read property \\\'children\\\' of undefined\\\",猜测使用了嵌套属性的原因,在页面中无法解析出具体属性值

Vue.js 自定义指令使用场景及案例

使用场景:代码复用和抽象的主要形式是组件;当需要对普通 DOM 元素进行底层操作,此时就会用到自定义指令;但是,对于大幅度的 DOM 变动,还是应该使用组件

Vue中插槽指令

意义就是在组件里留着差值方便后续组件内容新增,而且由于插件是写在父级中数据可以直接父级中传输而不需要传子再传父有些情况会减少写代码量,组件里没有设置插件名称,页面中插槽中写了插槽名称 结果页面中插槽不会被渲染

Vue常用指令

vue.js官方给自己的定为是数据模板引擎,并给出了一套渲染数据的指令。本文详细介绍了vue.js的常用指令。Vue.js 使用了基于 HTML 的模板语法最简单的使用vue的方式是渲染数据,渲染数据最常见的形式就是

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!