不想要网页默认的右键菜单栏,怎么封装一个可以自定义的右键菜单组件?

说在前面

网页的功能和用途可能各不相同,在传统右键菜单栏中无法满足每个用户的个性化需求。通过自定义右键菜单栏,用户可以根据自己的需求添加、调整和删除菜单选项,以实现个性化定制。通过自定义右键菜单栏,可以为用户提供快速访问常用功能和操作的便捷方式,从而提高用户体验。

效果展示

不想要网页默认的右键菜单栏,怎么封装一个可以自定义的右键菜单组件?_第1张图片

不想要网页默认的右键菜单栏,怎么封装一个可以自定义的右键菜单组件?_第2张图片

实现原理

1、oncontextmenu事件了解一下

oncontextmenu 事件在元素中用户右击鼠标时触发并打开上下文菜单。

oncontextmenu是一个DOM事件,它在用户右键点击时触发。可以通过在HTML元素上添加oncontextmenu属性来指定右键菜单的处理函数。

例如,在一个按钮元素上添加oncontextmenu属性:

<button oncontextmenu="showContextMenu(event)">右键点击我button>

在这个示例中,当用户右键点击按钮时,会调用showContextMenu函数,并将事件对象作为参数传递给该函数。

在JavaScript代码中,可以定义showContextMenu函数来处理右键菜单的显示和操作:

function showContextMenu(event) {
  event.preventDefault(); // 阻止默认的右键菜单弹出
  // 显示自定义右键菜单
  // ...
}

在showContextMenu函数中,通过调用event.preventDefault()方法阻止浏览器默认的右键菜单弹出。然后,可以根据需要执行自定义的逻辑,例如显示自定义的右键菜单。

2、在指定容器元素自定义右键菜单

首先,使用getElementById方法获取绑定右键菜单的DOM元素和右键菜单的容器元素。如果获取失败,则直接返回。

const dom = document.getElementById(this.domId);
if (!dom) return;

接着,给绑定右键菜单的DOM元素添加oncontextmenu事件处理函数。当用户触发右键点击事件时,首先调用hideAllMenu方法隐藏所有的右键菜单,然后通过event.preventDefault方法禁止默认行为,防止浏览器弹出默认的右键菜单。接下来,计算出鼠标指针相对于文档顶部和左侧的位置,并设置右键菜单的位置和显示状态。

const that = this;
dom.oncontextmenu = function (e) {
    that.hideAllMenu(that.uid);
    // 自定义body元素的鼠标事件处理函数
    e = e || window.event;
    e.preventDefault();
    let scrollTop =
        document.documentElement.scrollTop ||
        document.body.scrollTop; // 获取垂直滚动条位置
    let scrollLeft =
        document.documentElement.scrollLeft ||
        document.body.scrollLeft; // 获取水平滚动条位置
    menu.style.display = "block";
    menu.style.left = e.clientX + scrollLeft + "px";
    menu.style.top = e.clientY + scrollTop + "px";
};

最后,给document对象添加onclick事件处理函数。当用户在其他位置点击鼠标时,调用hideAllMenu方法隐藏所有的右键菜单。

document.onclick = function () {
    that.hideAllMenu();
};
hideAllMenu(id) {
    const jMenu = document.getElementsByClassName("j-mouse-menu");
    for (let i = 0; i < jMenu.length; i++) {
        if (jMenu[i].id != id) jMenu[i].style.display = "none";
    }
},

3、封装成一个组件

(1)template菜单模板
<div :id="uid" class="j-mouse-menu">
    <slot name="header">slot>
    <ul>
        <li
            v-for="menuItem in menu"
            :key="menuItem.id"
            @click="menuClick(menuItem)"
        >
            {{ menuItem.label }}
        li>
    ul>
    <slot name="body">slot>
    <slot name="footer">slot>
div>

使用:id="uid"绑定了组件的id属性,该属性值由组件实例的uid属性提供。这样可以确保每个组件实例都有唯一的id。

然后,给组件添加了j-mouse-menu类,用于设置组件的样式。

在组件的内容区域中,使用了Vue的插槽机制。

  • 定义了一个名为"header"的插槽,用于放置菜单栏的头部内容。
    • 标签下使用了v-for指令遍历menu数组,生成菜单项。
    • 菜单项使用
    • 标签表示,并通过:key绑定了唯一的menuItem.id作为key值。
    • 通过@click绑定了menuClick方法,该方法会在点击菜单项时被调用。
    • 菜单项的显示文本使用插值语法{{ menuItem.label }}来动态显示。

    接下来,又定义了两个插槽:

    • 用于放置菜单栏的主体内容。
    • 用于放置菜单栏的底部内容。
    (2)props入参
    props: {
        domId: {
            type: String,
            default: "",
        },
        menu: {
            type: Array,
            default: () => {
                return [];
            },
        },
    },
    

    domId表示需要绑定右键菜单的DOM元素容器的id,menu表示右键菜单的选项列表,menu数据格式如下:

    [
        {
            id: "1",
            label: "菜单1"
        },
        {
            id: "2",
            label: "菜单2",
            click: this.test
        },
        {
            id: "3",
            label: "菜单3"
        },
        {
            id: "4",
            label: "菜单4"
        },
        {
            id: "5",
            label: "菜单5"
        }
    ]
    
    (3)菜单点击回调
    menuClick(item) {
        if (item.click) {
            item.click(item);
            return;
        }
        this.$emit("menuClick", item);
    },
    

    首先判断item对象是否存在click属性。如果存在,则执行item.click(item),并将item作为参数传递给click函数。然后,返回结束方法的执行。

    如果item对象不存在click属性,即没有自定义的点击处理函数,那么就通过this.$emit(“menuClick”, item)语法触发一个名为"menuClick"的自定义事件,并将item作为参数传递给父组件。

    (4)完整组件代码
    <template>
        <div>
            <div :id="uid" class="j-mouse-menu">
                <slot name="header">slot>
                <ul>
                    <li
                        v-for="menuItem in menu"
                        :key="menuItem.id"
                        @click="menuClick(menuItem)"
                    >
                        {{ menuItem.label }}
                    li>
                ul>
                <slot name="body">slot>
                <slot name="footer">slot>
            div>
        div>
    template>
    
    <script>
    import { getUId } from "../../../utils/strTool";
    export default {
        name: "JMouseMenu",
        props: {
            domId: {
                type: String,
                default: "",
            },
            menu: {
                type: Array,
                default: () => {
                    return [];
                },
            },
        },
        data() {
            return {
                uid: "",
            };
        },
        created() {
            this.setUid();
        },
        mounted() {
            this.init();
        },
        methods: {
            setUid() {
                this.uid = "j-mouse-menu-" + getUId();
            },
            init() {
                // 自定义鼠标右键菜单栏
                const dom = document.getElementById(this.domId);
                if (!dom) return;
                const menu = document.getElementById(this.uid);
                const that = this;
                dom.oncontextmenu = function (e) {
                    that.hideAllMenu(that.uid);
                    // 自定义body元素的鼠标事件处理函数
                    e = e || window.event;
                    e.preventDefault();
                    let scrollTop =
                        document.documentElement.scrollTop ||
                        document.body.scrollTop; // 获取垂直滚动条位置
                    let scrollLeft =
                        document.documentElement.scrollLeft ||
                        document.body.scrollLeft; // 获取水平滚动条位置
                    menu.style.display = "block";
                    menu.style.left = e.clientX + scrollLeft + "px";
                    menu.style.top = e.clientY + scrollTop + "px";
                };
                // 鼠标点击其他位置时隐藏菜单
                document.onclick = function () {
                    that.hideAllMenu();
                };
            },
            hideAllMenu(id) {
                const jMenu = document.getElementsByClassName("j-mouse-menu");
                for (let i = 0; i < jMenu.length; i++) {
                    if (jMenu[i].id != id) jMenu[i].style.display = "none";
                }
            },
            menuClick(item) {
                if (item.click) {
                    item.click(item);
                    return;
                }
                this.$emit("menuClick", item);
            },
        },
    };
    script>
    
    <style lang="less" scoped>
    .j-mouse-menu {
        display: none;
        position: absolute;
        min-width: 8em;
        max-width: 15em;
        border: 1px solid #ccc;
        background: #eee;
        ul {
            margin: 5px 0;
            padding: 0;
        }
        li {
            height: 30px;
            line-height: 30px;
            color: #21232e;
            font-size: 12px;
            text-align: center;
            cursor: default;
            padding: 0;
            margin: 0;
            list-style-type: none;
            border-bottom: 1px dashed #cecece;
            &:hover {
                background-color: #cccccc;
            }
        }
    }
    style>
    
    (5)组件使用
    <j-mouse-menu
        :domId="'j-mouse-menu-view-content1'"
        :menu="myMenu"
        @menuClick="menuClick"
    >
        <template v-slot:header>
            <div class="menu-slot-header">JYeontudiv>
        template>
        <template v-slot:footer>
            <div class="menu-slot">
                
            div>
        template>
    j-mouse-menu>
    

    通过插槽自定义右键菜单的头部和底部内容,菜单列表通过menu参数传入子组件,并绑定菜单点击事件menuClick

    data(){
        return {
            myMenu: [
                {
                    id: "1",
                    label: "菜单1"
                },
                {
                    id: "2",
                    label: "菜单2",
                    click: this.test
                },
                {
                    id: "3",
                    label: "菜单3"
                },
                {
                    id: "4",
                    label: "菜单4"
                },
                {
                    id: "5",
                    label: "菜单5"
                }
            ]
        }
    },
    methods:{
        menuClick(menuItem) {
            alert("点击了:" + menuItem.label);
        },
        test(menuItem) {
            alert("test-" + menuItem.id);
        },
        alert(label) {
            alert("点击了:" + label);
        }
    }
    

    组件库

    组件文档

    目前该组件也已经收录到我的组件库,组件文档地址如下:
    http://jyeontu.xyz/jvuewheel/#/JMouseMenu

    组件内容

    组件库中还有许多好玩有趣的组件,如:

    • 悬浮按钮
    • 评论组件
    • 词云
    • 瀑布流照片容器
    • 视频动态封面
    • 3D轮播图
    • web桌宠
    • 贡献度面板
    • 拖拽上传
    • 自动补全输入框
    • 图片滑块验证

    等等……

    组件库源码

    组件库已开源到gitee,有兴趣的也可以到这里看看:https://gitee.com/zheng_yongtao/jyeontu-component-warehouse

    觉得有帮助的可以点个star~

    有什么问题或错误可以指出,欢迎pr~

    有什么想要实现的组件或想法可以联系我~

    公众号

    关注公众号『前端也能这么有趣』,获取更多有趣内容。

    发送『组件库』获取源码

    说在后面

    这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 ,平时也喜欢写些东西,既为自己记录 ,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 ,写错的地方望指出,定会认真改进 ,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 。

你可能感兴趣的:(前端,vue,组件开发,前端,vue,javascript)