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

news2024/11/23 8:32:11

效果

在这里插入图片描述

功能描述

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

示例

指令代码

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.coloradmin.cn/o/1836170.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在Linux系统中安装凸语言

凸语言在2023国产编程语言蓝皮书中的介绍如下&#xff1a; 凸语言gitee页面&#xff1a;凸语言: tu-lang 是一种动态类型编译型的通用编程语言, 已实现自举 (gitee.com) 使用git克隆源码&#xff1a; git clone https://github.com/tu-lang/tu.git 安装凸语言环境&#xff1a…

1.4k star 项目 CMakeTutorial 阅读和点评

1.4k star 项目 CMakeTutorial 阅读和点评 文章目录 1.4k star 项目 CMakeTutorial 阅读和点评0. 概要1. CUDA 目录2. FindPackage 目录3. Installation 目录4. PackageManage 目录5. PythonExtension 目录6. ImportExternalProject 目录总结 0. 概要 在 github 搜索关键字 CM…

【ARMv8/ARMv9 硬件加速系列 2.4 -- ARM NEON Q寄存器与V寄存器的关系】

文章目录 Q 与 V 的关系向量寄存器 v 的使用赋值操作寄存器赋值总结Q 与 V 的关系 在ARMv8/v9架构中,v寄存器和q寄存器实际上是对相同的物理硬件资源的不同称呼,它们都是指向ARM的SIMD(单指令多数据)向量寄存器。这些寄存器用于高效执行向量和浮点运算,特别是在多媒体处理…

03-ES6新语法

1. ES6 函数 1.1 函数参数的扩展 1.1.1 默认参数 function fun(name,age17){console.log(name","age); } fn("张美丽",18); // "张美丽",18 fn("张美丽",""); // "张美丽" fn("张美丽"); // &…

‘引爆增长·赋能十堰’第一届学习峰会在十堰东方汉宫国际酒店成功举办

‘引爆增长赋能十堰’第一届学习峰会在十堰东方汉宫国际酒店成功举办 2024年6月 17 至18 日&#xff0c;为期两天的“引爆增长赋能十堰”第一届学习交流峰会在湖北十堰东方汉宫国际酒店一号盛大举行&#xff0c;学习峰会现场&#xff0c;来自十堰地区及邻边地市的上百位实体企业…

网络基础

文章目录 网络发展协议网络分层结构局域网通信IP地址和MAC地址 网络发展 独立模式: 计算机之间相互独立 ; 网络互联: 多台计算机连接在一起, 完成数据共享因为人和人之间数据要互相协作&#xff0c;所以网络的出现是必然的&#xff0c;同时随着网络发展&#xff0c;必然会产生…

yt-dlp:强大的跨平台视频下载器

一、引言 在当今数字时代&#xff0c;视频已成为我们获取信息和娱乐的重要途径。然而&#xff0c;由于版权和网络限制&#xff0c;我们常常无法直接在本地保存我们喜爱的视频。幸运的是&#xff0c;有一个名为yt-dlp的命令行程序&#xff0c;它可以帮助我们从YouTube.com和其他…

NoSQL-Tidis支持分布式事务,兼容redis协议,使用tikv存储引擎,可水平扩展

项目repo地址 GitHub - yongman/tidis: Distributed transactional NoSQL database, Redis protocol compatible using tikv as backend Tidis是分布式数据库,支持redis协议,多种数据结构支持,编写语言为golang。 Tidis工作角色类似于TIDB,提供协议转换和数据结构计算,底…

Jenkins教程-3-github自动化测试任务构建

上一小节我们学习了Jenkins在windows和mac系统上安装搭建环境的方法&#xff0c;本小节我们讲解一下Jenkins构建github自动化测试任务的方法。 接下来我们以windows系统为例&#xff0c;讲解一下构建实际自动化测试任务的具体步骤。 安装git和github插件 点击进入Jenkins插件…

自杀行为的神经生物学认识

自杀行为的神经生物学认识 编译 李升伟 隐藏在自杀行为背后的大脑生化机制正引领人类对自杀的认识从黑暗步入光明。科学家希望未来这些机制能带来更好的治疗和预防策略。 基斯 • 范希林根&#xff08;Cornelis Van Heeringen&#xff09;第一次遇见瓦莱丽&#xff08; Va…

dmhs同步因目的端表自增列报错解决方法

dmhs同步因目的端表自增列报错解决方法 1 dmhs copy 装载数据时报错 HY000 CODE:-27232 配置源端捕获器cpt 1 dmhs copy 装载数据时报错 HY000 CODE:-2723 ERR:Only if specified in the column list and SET IDENTITY INSERT is ON, then identity column could be assigned …

04-对原生app应用中的元素进行定位

本文介绍对于安卓原生app应用中的元素如何进行定位。 一、uiautomatorviewer uiautomatorviewer是Android-SDK自带的一个元素定位工具&#xff0c;非常简单好用&#xff0c;可以使用该工具查看app应用中的元素属性&#xff0c;帮助我们在代码中进行元素定位。 1&#xff09;使…

oracle12c到19c adg搭建(三)oracle19c数据库软件安装

由于这里的19c是做备库所以我们只安装软件不用创建实例&#xff0c;实例由主库同步过来 解压软件到安装目录 注意19c得db要直接解压到19c得软件安装目录 [rooto12u19p software]# ls V982063-01.zip [rooto12u19p software]# ls -ll total 2987996 -rw-r--r-- 1 root ro…

第二证券:英伟达“利空”?!黄仁勋逢高又卖超2亿

时隔9个月&#xff0c;黄仁勋再次套现英伟达&#xff0c;股价小幅下跌。 当地时间6月17日周一&#xff0c;美股三大股指全线收涨。到收盘&#xff0c;道指涨0.49%&#xff0c;纳指涨0.95%&#xff0c;标普500指数涨0.77%。 英伟达CEO黄仁勋时隔9个月再度出售英伟达股票。最新…

netty服务端与客户端的启动流程

如图所示&#xff0c;右侧是服务端Server&#xff0c;左侧是客户端Client 要点说明&#xff1a; 1.在Server中&#xff0c;在NioEventLoopGroup()中&#xff0c;会有1个selector和线程在不断循环&#xff0c;等待是否有accept事件&#xff0c;在accept事件发生后&#xff0c;才…

所以spring mvc异常处理工作原理是啥

文章目录 spring mvc异常处理&#xff08;源码分析&#xff09;概述原理&#xff08;源码角度&#xff09;模拟debug前期提要分析4个map4个map的初始化为什么需要基于mappedMethods缓存 总结一下 spring mvc异常处理&#xff08;源码分析&#xff09; 概述 spring mvc有下面三…

ubuntu 18.04 安装vnc

如何在Ubuntu 18.04安装VNC | myfreax sudo apt install xfce4 xfce4-goodies xorg dbus-x11 x11-xserver-utils sudo apt install tigervnc-standalone-server tigervnc-common vncserver sudo apt install xfce4 xfce4-goodies xorg dbus-x11 x11-xserver-utils sudo apt ins…

kotlin数组

1、kotlin中的数组与java数组比较&#xff1a; 2、创建 fun main() {// 值创建val a intArrayOf(1,2,3)// 表达式创建val b IntArray(3){println("it: ${it}")it1}println("a数组&#xff1a;${a.contentToString()}, 长度&#xff1a;${a.size}")prin…

乡村养老服务管理系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;医疗人员管理&#xff0c;乡村志愿者管理&#xff0c;文娱活动管理&#xff0c;活动报名管理&#xff0c;医疗保健管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;文娱活…

【vue baidu-map】解决更新数据,bm-marker显示不完全问题

实现效果&#xff1a; 问题&#xff1a;切换上面基地tab键&#xff0c;导致地图图标展示不完全&#xff1b;刷新页面就可以正常展示。判断是<bm-marker>标记元素没有动态刷新dom元素引起的问题。 方案&#xff1a;this.$nextTick({}) this.$nextTick(()>{this.equipm…