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

效果

在这里插入图片描述

功能描述

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

示例

指令代码

export const dragDialog = {inserted: function (el, { value, minWidth = 400, minHeight = 200 }) {// 让弹窗居中let dialogHeight = el.clientHeight ?? 0let dialogWidth = el.clientWidth ?? 0// 获取可视区域的宽高let windowWidth = document.documentElement.clientWidth ?? 0let windowHeight = document.documentElement.clientHeight ?? 0// 弹窗的可移动范围let leftMax = windowWidth - dialogWidthlet topMax = windowHeight - dialogHeight//还需要判断是否传入了top,left值let { top, center } = valuelet left = (windowWidth - dialogWidth) / 2if (!center) {// 没有设置centerif (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) returnlet 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) returnlet offsetTopHeader = el_header_rect.top - 0// 判断当前元素是否是可拖拽的头部元素if (headerHeight > e.pageY - offsetTopHeader) {// 是头部,拖拽相关// 获取到鼠标与被拖拽节点的相对位置let disx = e.pageX - el.offsetLeftlet disy = e.pageY - el.offsetTop// 获取弹窗的宽高let width = el.clientWidth ?? 0let height = el.clientHeight ?? 0// 设置其他弹窗的z-index 100let maxZIndex = 100document.querySelectorAll('.kl-dialog-container').forEach((item) => {let zIndex = item.style.zIndexzIndex = zIndex ? zIndex - 0 : 100if (zIndex > maxZIndex) {maxZIndex = zIndex}})el.style.zIndex = maxZIndex + 1document.onmousemove = function (e) {const el_rect = el.getBoundingClientRect()if (!el_rect) return// 获取弹窗到可视区域的距离let offsetTopEl = el_rect.top - 0let offsetLeftEl = el_rect.left - 0let left = e.pageX - disxlet 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 = nulldocument.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')},// 给每个弹窗添加一个idinitId() {this.klMaskId = this.createId()this.klDialogId = this.createId()},// 将当前弹窗的z-index设置为最高async setZIndex() {let { klDialogId } = thisawait this.$nextTick()let els = document.querySelectorAll('.kl-dialog-container')let maxZIndex = 100els.forEach((item) => {let zIndex = item.style.zIndexzIndex = zIndex ? zIndex - 0 : 100if (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.mzph.cn/bicheng/29647.shtml

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

相关文章

Vue61-消息订阅与发布-任意组件之间的通信

一、原理图 原生的JS不能实现订阅与发布&#xff0c;要借助第三方库&#xff1a;pubsub-js&#xff08;任何一个框架都能用&#xff01;&#xff09; 二、案例实现 school组件&#xff0c;需要数据&#xff08;订阅消息&#xff09;&#xff0c;student组件提供数据&#xff0…

MySQL 高级 - 第十二章 | 数据库的设计规范

目录 第十二章 数据库的设计规范12.1 为什么需要数据库设计12.2 范式12.2.1 范式简介12.2.2 范式都包括哪些12.2.3 键和相关属性的概念12.2.4 第一范式&#xff08;1st NF&#xff09;12.2.5 第二范式&#xff08;2nd NF&#xff09;12.2.6 第三范式&#xff08;3rd NF&#xf…

onnx进阶算子优化

一、定义 如何保证pytorch 模型顺利转为onnx. 前言pytorch 算子是如何与onnx 算子对齐的&#xff1f;Asinh 算子出现于第 9 个 ONNX 算子集。PyTorch 在 9 号版本的符号表文件中是怎样支持这个算子的&#xff1f;BitShift 算子出现于第11个 ONNX 算子集。PyTorch 在 11 号版本…

4M-21:霸气侧漏高效的20+多模态AI模型

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则…

软件设计不是CRUD(22):在流式数据处理系统中进行业务抽象落地——设计思考

(接上文《软件设计不是CRUD(21):在流式数据处理系统中进行业务抽象落地——需求分析》) 那么思考到这里我们就能做一些关于设计思路的总结: 每一个独立的数据处理流,就是数据采集系统中的一个功能。这个功能具备一个静态的控制逻辑(当然控制逻辑也可以是动态的,本文不…

嵌入式技术学习——c51单片机——蜂鸣器

一、蜂鸣器介绍 蜂鸣器时一种将电信号转化成声音信号的器件&#xff0c;常用来产生设备的按键音&#xff0c;报警音等提示信号。 蜂鸣器分为有源蜂鸣器&#xff0c;无源蜂鸣器 。 有源蜂鸣器&#xff1a;内部自带震荡源&#xff0c;将正负极街上直流电压即可持续发声&#x…

深度学习(十)——神经网络:非线性激活

一、Padding Layers简介 nn.ZeroPad2d&#xff1a;在输入的tensor数据类型周围用0进行填充 nn.ConstantPad2d&#xff1a;在输入的tensor数据类型周围用常数进行填充 这个函数的主要作用是对输入的图像进行填充&#xff0c;但里面所有功能都能用nn.Conv2d实现。 二、Non-li…

一文读懂OpenGVLab带来的最新视觉预训练框架

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则…

.NET周刊【6月第3期 2024-06-18】

国内文章 记一次 .NET某游戏币自助机后端 内存暴涨分析 https://www.cnblogs.com/huangxincheng/p/18243233 文章讨论了程序中非托管内存暴涨的问题。作者描述了友人发现内存问题并请他帮助分析的背景&#xff0c;利用WinDbg工具分析Linux平台上的内存泄漏情况。文章介绍了如…

潮玩宇宙大逃杀APP系统开发成品案例分享指南

这是一款多人游戏&#xff0c;玩家需要选择一个房间躲避杀手。满足人数后&#xff0c;杀手会随机挑选一个房间杀掉里面所有的参与者&#xff0c;其他房间的幸存者将平均瓜分被杀房间的元宝。玩家在选中房间后&#xff0c;倒计时结束前可以自由切换不同房间。 软件项目开发成品…

LabVIEW开发为什么沟通需求非常重要

在LabVIEW开发项目中&#xff0c;需求沟通是项目成功的基石。以下是需求沟通的重要性及其原因&#xff1a; 明确项目目标&#xff1a; 定义清晰的目标&#xff1a;通过与用户的沟通&#xff0c;可以明确项目的目标和范围&#xff0c;确保开发团队理解用户的实际需求&#xff0c…

【Android-Compose】流式布局FlowRow 不能居中对齐的一种解决办法

问题描述&#xff1a; 在安卓Compose 开发中使用LazyColumn 流式布局 FlowRow 有时候比延迟网格布局更灵活&#xff0c;但是也可能出现自动流向下一行之后&#xff0c;末尾处留下一些小空白。如图&#xff1a; 问题解决&#xff1a; 为了尽可能居中对齐&#xff0c;我们可…

专业技能篇---计算机网络篇

文章目录 前言计算机网络基础一、网络分层模型 HTTP一、从输入URL到页面显示发生了什么&#xff1f;二、Http的状态码有哪些&#xff1f;三、 HTTP与HTTPS有什么区别&#xff1f;四、URI 和 URL 的区别是什么?五、Cookie和Session有什么区别&#xff1f;六、GET与POST 前言 主…

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 …

洛谷 P3379:最近公共祖先(LCA)← RMQ+欧拉序

【题目来源】https://www.luogu.com.cn/problem/P3379【题目描述】 如题&#xff0c;给定一棵有根多叉树&#xff0c;请求出指定两个点直接最近的公共祖先。【输入格式】 第一行包含三个正整数 N,M,S&#xff0c;分别表示树的结点个数、询问的个数和树根结点的序号。 接下来 N−…

AI大模型的战场:通用大模型VS垂直大模型,谁会赢?

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

二、利用YOLOv8解决现实世界的问题

Ultralytics Solutions提供顶尖的YOLO模型应用&#xff0c;提供现实世界的解决方案如&#xff1a;目标记数&#xff0c;模糊和安全系统&#xff0c;提升效率和准确率在各种工业中。探索YOLOv8在实用性和有效性上的强大功能。 解决方案&#xff1a; 下面展示利用Ultralytics So…

使用vscode插件du-i18n处理前端项目国际化翻译多语言

前段时间我写了一篇关于项目国际化使用I18n组件的文章&#xff0c;Vue3 TS 使用国际化组件I18n&#xff0c;那个时候还没真正在项目中使用&#xff0c;需求排期还没有定&#xff0c;相当于是预研。 当时就看了一下大概怎么用&#xff0c;改了一个简单的页面&#xff0c;最近需…

【Python系列】Python 中的日期和时间处理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Vue3模拟国足18强赛抽签

Vue3国足18强赛抽签 国足遇到这个对阵&#xff0c;能顺利出现吗&#xff1f; 1、系统演示 Vue3模拟国足18强赛抽签 2、关键代码 开始抽签 <script setup> import FenDang from "/components/chouqian/FenDang.vue"; import {ref} from "vue";le…