vue中通过自定义指令实现一个可拖拽,缩放的弹窗

效果

在这里插入图片描述

功能描述

  • 按住头部可拖拽
  • 鼠标放到边框,可缩放
  • 多层重叠
  • 丰富的插槽,易于扩展

示例

指令代码

export const dragDialog = {
    inserted: function (el, { value, minWidth = 400, minHeight = 200 }) {
        // 让弹窗居中
        let dialogHeight = el.clientHeight ?? 0
        let dialogWidth = el.clientWidth ?? 0

        // 获取可视区域的宽高
        let windowWidth = document.documentElement.clientWidth ?? 0
        let windowHeight = document.documentElement.clientHeight ?? 0

        // 弹窗的可移动范围
        let leftMax = windowWidth - dialogWidth
        let topMax = windowHeight - dialogHeight

        //还需要判断是否传入了top,left值
        let { top, center } = value

        let left = (windowWidth - dialogWidth) / 2

        if (!center) {
            // 没有设置center
            if (top.includes('%') || top.includes('px')) {
                el.style.top = top
            } else {
                el.style.top = top + 'px'
            }

            el.style.left = left + 'px'
        } else {
            el.style.top = (windowHeight - dialogHeight) / 2 + 'px'
            el.style.left = (windowWidth - dialogWidth) / 2 + 'px'
        }

        const el_header = el.querySelector('.kl-dailog-header')
        // 只有点击头部才能拖拽
        if (!el_header) return
        let headerHeight = el_header.clientHeight - 0
        // 缩放相关
        el.onmousemove = function (e) {
            if(!e) return
            // 判断当前鼠标是否处于可以拖拽的边缘,不包含头部
            if (e.clientX > el.offsetLeft + el.clientWidth - 10 || el.offsetLeft + 10 > e.clientX) {
                el.style.cursor = 'w-resize'
            } else if (
                el.scrollTop + e.clientY >
                el.offsetTop + el.clientHeight - 10 - headerHeight
            ) {
                el.style.cursor = 's-resize'
            } else {
                el.style.cursor = 'default'
            }

            el.onmousedown = (e) => {
                if(!e) return
                // 获取头部的宽高以及到可视区域的距离
                const el_header_rect = el_header.getBoundingClientRect()

                if (!el_header_rect) return
                let offsetTopHeader = el_header_rect.top - 0

                // 判断当前元素是否是可拖拽的头部元素
                if (headerHeight > e.pageY - offsetTopHeader) {
                    // 是头部,拖拽相关
                    // 获取到鼠标与被拖拽节点的相对位置
                    let disx = e.pageX - el.offsetLeft
                    let disy = e.pageY - el.offsetTop

                    // 获取弹窗的宽高
                    let width = el.clientWidth ?? 0
                    let height = el.clientHeight ?? 0

                    // 设置其他弹窗的z-index 100
                    let maxZIndex = 100
                    document.querySelectorAll('.kl-dialog-container').forEach((item) => {
                        let zIndex = item.style.zIndex
                        zIndex = zIndex ? zIndex - 0 : 100
                        if (zIndex > maxZIndex) {
                            maxZIndex = zIndex
                        }
                    })
                    el.style.zIndex = maxZIndex + 1

                    document.onmousemove = function (e) {
                        const el_rect = el.getBoundingClientRect()

                        if (!el_rect) return

                        // 获取弹窗到可视区域的距离
                        let offsetTopEl = el_rect.top - 0
                        let offsetLeftEl = el_rect.left - 0

                        let left = e.pageX - disx
                        let top = e.pageY - disy

                        // 对弹窗的位置进行限制
                        if (offsetTopEl < 0 || top < 0) {
                            top = 0
                        }

                        if (offsetLeftEl < 0 || left < 0) {
                            left = 0
                        }

                        if (offsetTopEl + height > windowHeight || top > topMax) {
                            top = windowHeight - height
                        }

                        if (offsetLeftEl + width > windowWidth || left > leftMax) {
                            left = windowWidth - width
                        }

                        // 重新设置被拖拽节点的位置
                        el.style.left = left + 'px'
                        el.style.top = top + 'px'
                    }
                    document.onmouseup = function () {
                        document.onmousemove = document.onmouseup = null
                    }
                } else {
                    const clientX = e.clientX // 鼠标点击时的X坐标
                    const clientY = e.clientY // 鼠标点击时的Y坐标
                    let elW = el.clientWidth // 当前元素的宽度
                    let elH = el.clientHeight // 当前元素的高度
                    let EloffsetLeft = el.offsetLeft // 元素距离左边的距离
                    let EloffsetTop = el.offsetTop // 元素距离顶部的距离
                    el.style.userSelect = 'none'
                    let ELscrollTop = el.scrollTop // 元素滚动条距离顶部的距离
                    // 不是头部,缩放相关
                    document.onmousemove = function (e) {
                        e.preventDefault() // 移动时禁用默认事件
                        //左侧鼠标拖拽位置
                        if (clientX > EloffsetLeft && clientX < EloffsetLeft + 10) {
                            //往左拖拽
                            if (clientX > e.clientX) {
                                el.style.width = elW + (clientX - e.clientX) * 2 + 'px'
                                el.style.left = EloffsetLeft - (clientX - e.clientX) + 'px'
                            }
                            //往右拖拽
                            if (clientX < e.clientX) {
                                if (el.clientWidth < minWidth) {
                                } else {
                                    el.style.width = elW - (e.clientX - clientX) * 2 + 'px'
                                    el.style.left = EloffsetLeft + (e.clientX - clientX) + 'px'
                                }
                            }
                        }

                        //右侧鼠标拖拽位置
                        if (clientX > EloffsetLeft + elW - 10 && clientX < EloffsetLeft + elW) {
                            //往左拖拽
                            if (clientX > e.clientX) {
                                if (el.clientWidth < minWidth) {
                                } else {
                                    el.style.width = elW - (clientX - e.clientX) * 2 + 'px'
                                    el.style.left = EloffsetLeft + (clientX - e.clientX) + 'px'
                                }
                            }

                            //往右拖拽
                            if (clientX < e.clientX) {
                                el.style.width = elW + (e.clientX - clientX) * 2 + 'px'
                                el.style.left = EloffsetLeft + (clientX - e.clientX) + 'px'
                            }
                        }
                        //底部鼠标拖拽位置
                        if (
                            ELscrollTop + clientY > EloffsetTop + elH - 20 &&
                            ELscrollTop + clientY < EloffsetTop + elH
                        ) {
                            //往上拖拽
                            if (clientY > e.clientY) {
                                if (el.clientHeight < minHeight) {
                                } else {
                                    el.style.height = elH - (clientY - e.clientY) * 2 + 'px'
                                    el.style.top = EloffsetTop + (clientY - e.clientY) + 'px'
                                }
                            }
                            //往下拖拽
                            if (clientY < e.clientY) {
                                el.style.height = elH + (e.clientY - clientY) * 2 + 'px'
                                el.style.top = EloffsetTop + (clientY - e.clientY) + 'px'
                            }
                        }
                    }
                    //拉伸结束
                    document.onmouseup = function (e) {
                        document.onmousemove = null
                        document.onmouseup = null
                    }
                }
            }
        }
    },
    // 指令销毁
    unbind(el) {},
}

vue组件代码

<template>
    <!-- 添加弹窗的动画 -->
    <!-- <transition name="kl-dialog"> -->
        <div class="kl-dialog" v-if="dialogVisible">
            <!-- 遮罩 -->
            <div class="kl-mask" :id="klMaskId" v-if="modal" @click="close"></div>
            <!-- 弹窗 -->
            <!-- 这儿需要mousedown来控制顺序 -->
            <div
                :id="klDialogId"
                :class="[
                    'kl-dialog-container',
                    'resize-container',
                    nobg ? 'kl-dialog-container-bg-no' : '',
                ]"
                v-dragDialog="{ top: top, center: center }"
                :style="{ width: width, top: top }"
                @mousedown="setZIndex"
            >
                <!-- 弹窗头部 -->
                <slot name="header">
                    <!-- 必须要有这个kl-dailog-header类才能拖拽 -->
                    <div class="kl-dailog-header cc">
                        <div class="kl-dailog-header-title">
                            {{ title }}
                        </div>
                        <div class="kl-dailog-header-close" @click="close">
                            <i class="el-icon-close kl-dailog-header-close-icon"></i>
                        </div>
                    </div>
                </slot>

                <!-- 弹窗中间内容 -->
                <slot> default </slot>

                <!-- 弹窗底部 -->
                <slot name="footer">
                    <div class="kl-dailog-footer">
                        <el-button @click="close">取消</el-button>
                        <el-button type="primary" @click="determine">确定</el-button>
                    </div>
                </slot>
            </div>
        </div>
    <!-- </transition> -->
</template>

<script>
export default {
    name: 'klDialog',
    props: {
        // 去除主题背景色
        nobg: {
            type: Boolean,
            default: false,
        },
        // 控制显示隐藏
        dialogVisible: {
            type: Boolean,
            default: false,
        },
        // 是否显示遮罩
        modal: {
            type: Boolean,
            default: true,
        },
        // 头部标题
        title: {
            type: String,
            default: '',
        },
        // 弹窗宽
        width: {
            type: String,
            default: '30%',
        },
        // 距离顶部的距离
        top: {
            type: String,
            default: '20%',
        },
        center: {
            type: Boolean,
            default: false,
        },
    },
    data() {
        return {
            klMaskId: '',
            klDialogId: '',
        }
    },
    created() {
        this.init()
    },
    beforeDestroy() {
        // console.log('beforeDestroy');
    },

    watch: {
        dialogVisible(val) {
            if (val) {
                this.setZIndex()
            }
        },
    },

    methods: {
        // 确定
        determine() {
            this.$emit('determine')
        },
        // 关闭弹窗
        close() {
            this.$emit('close')
        },

        // 给每个弹窗添加一个id
        initId() {
            this.klMaskId = this.createId()
            this.klDialogId = this.createId()
        },

        // 将当前弹窗的z-index设置为最高
        async setZIndex() {
            let { klDialogId } = this
            await this.$nextTick()
            let els = document.querySelectorAll('.kl-dialog-container')

            let maxZIndex = 100
            els.forEach((item) => {
                let zIndex = item.style.zIndex
                zIndex = zIndex ? zIndex - 0 : 100
                if (zIndex > maxZIndex) {
                    maxZIndex = zIndex
                }
            })

            let el = document.querySelector('#' + klDialogId)
            if (el) {
                el.style.zIndex = maxZIndex + 1
            }
        },

        // 初始化
        init() {
            this.initId()
            // 设置当前的弹窗层级最高
            this.setZIndex()
        },
    },
}
</script>

<style lang="scss" scoped>
.kl-mask {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vw;
    background-color: rgba(0, 0, 0, 0.5);
    z-index: 100;
}
.kl-dialog-container {
    position: fixed;
    border-radius: 2px;
    box-shadow: 0 1px 3px rgb(0 0 0 / 30%);
    box-sizing: border-box;
    background-color: #fff;
    z-index: 100;
}
.kl-dialog-container-bg-no {
    box-shadow: none;
    background: none;
}
.kl-dialog-container-center {
    left: 50% !important;
    top: 50% !important;
    transform: translate(-50%, -50%) !important;
}

.kl-dailog-header {
    padding: 0 20px;
    height: 54px;
    line-height: 54px;
    position: relative;
    font-size: 18px;
    font-weight: 500;
    user-select: none;
}
.kl-dailog-header-close {
    position: absolute;
    top: 50%;
    right: 20px;
    transform: translateY(-50%);
    cursor: pointer;
}
.kl-dailog-header-close-icon {
    color: #aaa;
}
.kl-dailog-footer {
    padding: 0 20px;
    height: 70px;
    line-height: 70px;
    text-align: right;
}

.cc {
    cursor: move;
}

// 弹窗动画
.kl-dialog-enter-active,
.kl-dialog-leave-active {
    transition: all 0.3s;
}

.kl-dialog-enter,
.kl-dialog-leave-to {
    opacity: 0;
    transform: translate(300px,300px);
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/720924.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

和鲸101计划:以神经计算建模培训,助力北大学术人才培养

探索与求知&#xff0c;培养与传承。 让青年人更早地触摸到科学研究的前沿&#xff0c;便能吸引更多人才投身于学科建设。 11月4日&#xff0c;由北京大学信息处理实验室开展进行&#xff0c;北京大学心理与认知科学学院院长吴思教授及课题组成员授课的第二届神经计算建模及编…

C#心跳机制客户端

窗体&#xff08;richTextBox右显示聊天&#xff09; 步骤 点击链接按钮 tcpclient客户端步骤 1创建客户端对象 2连接服务器connect 3创建网络基础流发消息 .write发消息 4 创建网络基础流接消息 .read接消息 5 断开连接…

Spring注解----------@Deprecated

情景&#xff1a; 在我们开发过程中&#xff0c;有时候会遇到我们需要将几个类中的方法集中到一个类中&#xff0c;但是我们又不希望把我们的原来的类删掉&#xff08;就是单纯的不想删除&#xff0c;都是我写的代码我不想杀死我的结晶&#xff0c;不能说我写的是shi&#xff…

【C语言】--- 常见调试信息预处理器宏

在编程的艺术世界里&#xff0c;代码和灵感需要寻找到最佳的交融点&#xff0c;才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里&#xff0c;我们将共同追寻这种完美结合&#xff0c;为未来的世界留下属于我们的独特印记。 【C语言】--- 常见调试信息预处理器宏 开…

Multisim仿真之万用表、安捷伦万用表、信号发生器操作方法

1、XMM是安捷伦示波器 如下图所示&#xff0c;实物安捷伦的外围3个插孔对应于 XMM图标示波器的右侧3个引脚&#xff0c;上下一一对应 2、函数信号发生器XFG 如下图所示&#xff0c;COM就是GND&#xff0c;正负的意思就是相对于GND而言&#xff0c;有正负电压&#xff1b; 3、…

vivado PIP or SITE_PIP、PKGPIN_BYTEGROUP

PIP是Xilinx部件上用于路由连接或网络的设备对象。PIP 称为ARC的连接多路复用器可以编程为将一根电线连接到 另一个&#xff0c;从而将节点连接在一起&#xff0c;以形成中特定NET所需的路由 设计。 SITE_PIP&#xff0c;也称为路由BEL&#xff0c;是SITE内部的连接多路复用器&…

JavaFX按钮

当用户单击按钮时&#xff0c;JavaFX Button类可以触发事件。Button类扩展了Labeled类&#xff0c;可以显示文本&#xff0c;图像或两者都可以。 以下代码显示了如何向Button添加单击操作侦听器。 import javafx.application.Application; import javafx.event.ActionEvent; im…

【信息学奥赛】CSP-J/S初赛03 计算机网络与编程语言分类

第1节 计算机网络基础 1.1 网络的定义 所谓计算机网络&#xff0c;就是利用通信线路和设备&#xff0c;把分布在不同地理位置上的多台计算机连 接起来。计算机网络是现代通信技术与计算机技术相结合的产物。 网络中计算机与计算机之间的通信依靠协议进行。协议是计算机收、发…

Postman文件数据导入导出

前言 不同的接口测试工具如Postman、Apipost、Apifox创建的接口文档都是互通的&#xff0c;都可以互相兼容使用。我们就不需要在3个不同测试工具都去创建&#xff0c;只要在一个工具上创建&#xff0c;想要在其他接口测试工具上使用就运用导入和导出功能即可。 Postman、Apip…

2024.6.18 作业 xyt

今日作业&#xff1a; 1. 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果…

【shell脚本速成】for 嵌套和和 if 高级用法

文章目录 一、for嵌套二、for与数组三、if高级用法3.1、条件符号使用双圆括号&#xff0c;可以在条件中植入数学表达式 if (())3.2、使用双方括号,可以在条件中使用通配符 四、简写if五、与文件存在与否的判断六、课后练习 &#x1f308;你好呀&#xff01;我是 山顶风景独好 &…

智能网站管理系统

智能网站管理系统&#xff0c;即智能化的网站管理工具&#xff0c;是为了提高网站管理效率和简化操作流程而开发的一种软件系统。它集合了各种先进的技术和功能&#xff0c;为网站管理员提供了一套强大而可靠的解决方案。 智能网站管理系统的核心功能是网站内容管理。传统的网站…

MySQL 基本语法讲解及示例(上)

第一节&#xff1a;MySQL的基本操作 1. 创建数据库 在 MySQL 中&#xff0c;创建数据库的步骤如下&#xff1a; 命令行操作 打开 MySQL 命令行客户端或连接到 MySQL 服务器。 输入以下命令创建一个数据库&#xff1a; CREATE DATABASE database_name;例如&#xff0c;创建一…

(资料收藏)王阳明传《知行合一》共74讲,王阳明知行合一音频讲解资料

今天给大家带来的不是软件&#xff0c;而是一份精神食粮——《知行合一》的教程福利。这可不是一般的教程&#xff0c;它关乎心灵&#xff0c;关乎智慧&#xff0c;关乎我们如何在纷繁复杂的世界中找到自己的位置。 咱们得聊聊王阳明&#xff0c;这位明代的大儒&#xff0c;他…

看完这篇文章你才能了解什么是大模型

引言 近年来&#xff0c;人工智能&#xff08;AI&#xff09;技术迅速崛起&#xff0c;成为全球科技领域的热门话题。大模型&#xff08;Large Language Model&#xff09;技术以其庞大的参数和复杂的结构&#xff0c;为AI提供了强大的计算和学习能力&#xff0c;推动着AI技术…

【proteus仿真】基于51单片机的秒表设计

【proteus仿真】基于51单片机的秒表设计 资料获取在文章结尾处&#xff01; 更多资料获取链接&#xff1a; https://docs.qq.com/sheet/DTExIc2dPUUJ5enZZ?tabBB08J2 1.资料内容 源码proteus仿真图 演示视频&#xff1a; 【proteus仿真】基于51单片机的秒表设计_哔哩哔…

​【数据结构与算法】冒泡排序:简单易懂的排序算法解析

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 一、引言 二、冒泡排序原理 &#x1f343;基本思想&#xff1a; &#x1f343;算法…

为什么传统 CNN 可能无法进行基于纹理的分类?

作者&#xff1a;Mayank Gubba、Mohammed Faisal、Trapti Kalra、Vijay Pandey 将纹理分析与深度学习结合使用对于在机器视觉任务中取得更好的结果起着重要作用。在第一篇博客中&#xff0c;我们讨论了“纹理”的基础知识、不同类型的纹理以及纹理分析在解决实际计算机视觉任务…

Windows系统下制作Windows 11系统U盘启动及安装指导

Windows系统下制作Windows 11系统U盘启动及安装指导 一、准备工作 U盘不得小于8G(推荐使用usb3.0接口)&#xff1b;下载好对应的系统镜像&#xff1b;下载RUFUS或者软通碟U盘制作启动软件&#xff1b; 二、Windows操作系统下制作U盘启动&#xff08;这里以使用RUFUS软件为例&…

Java对象头的组成

介绍对象头之前先说一下Java对象内部的组成结构&#xff1a; 1&#xff0c;成员变量&#xff08;Data1...DataN&#xff09; 2, 对象头 Java对象头的组成&#xff08;根据对象头分析对象状态借此优化代码&#xff09; <dependency> <groupId>org.openjdk.jol&l…