ant-design-vue 1.x版本自定义可拖拽弹框

一、研发拖拽弹窗的起因

最近在使用宝塔的时候,发现他的弹框是可以拖拽的,于是就像自己的平台弹框也增加这个功能。

二、分析

ant-design-vue的文档发现没有相关的兼容实现

三、解决方案
3.1、使用vue2.x带的directives来进行自定义指令代码如下:
javascript 复制代码
directives: {
  dialogDrag: {
    /**
     * 拖拽指令的绑定函数
     * 用于给模态框添加拖拽功能
     * 
     * @param {HTMLElement} el - 指令绑定的元素(a-modal容器)
     * @param {Object} binding - 包含指令参数的对象
     * @param {Object} vnode - Vue虚拟节点
     * @param {Object} oldVnode - 旧的虚拟节点
     */
    bind(el, binding, vnode, oldVnode) {
      const value = binding.value
      // 如果拖拽功能被禁用或处于全屏模式,则不执行拖拽初始化
      if (value === false || vnode.context.innerFullscreen) return
      
      // 获取拖拽内容头部元素(用于触发拖拽事件)
      const dialogHeaderEl = el.querySelector('.ant-modal-header')
      // 获取模态框主体元素(用于设置位置样式)
      const dragDom = el.querySelector('.ant-modal')
      
      // 保存原始光标样式,用于后续恢复
      vnode.context.cursor = dialogHeaderEl.style.cursor
      // 设置拖拽头部的光标为移动样式
      dialogHeaderEl.style.cursor = 'move'
      
      // 设置模态框为绝对定位,以便进行拖拽定位
      dragDom.style.position = 'absolute'
      // 重置margin-top,避免影响定位计算
      dragDom.style.marginTop = 0
      
      // 获取模态框宽度,处理百分比和像素值
      let width = dragDom.style.width
      if (width.includes('%')) {
        // 如果是百分比,转换为像素值
        width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100)
      } else {
        // 如果是像素值,提取数值部分
        width = +width.replace(/\px/g, '')
      }
      
      // 获取模态框实际宽度,如果计算宽度大于实际宽度则使用实际宽度
      let modalClientWidth = el.getElementsByClassName('ant-modal')[0].clientWidth
      if(width > modalClientWidth && modalClientWidth > 0){
        width = modalClientWidth;
      }
      
      // 使用window.innerWidth获取视口宽度
      const viewportWidth = window.innerWidth;
      // 计算居中位置
      const leftPosition = ((viewportWidth - width) / 2);

      // 确保left值不会为负数,防止对话框移出屏幕
      dragDom.style.left = Math.max(0, leftPosition) + 'px';
      // 调用模态框实例的onDrag方法,初始化拖拽事件监听
      vnode.context.onDrag();
    }
  },
},
3.2、元素中绑定该自定义功能:
html 复制代码
<a-modal
    ref="modal"
    :class="getClass(modalClass)"
    :style="getStyle(modalStyle)"
    :visible="visible"
    v-bind="_attrs"
    v-on="$listeners"
    @ok="handleOk"
    @cancel="handleCancel"
    destroyOnClose
    v-dialog-drag
  ></a-modal>
3.3、watch监听弹框是否显示
javascript 复制代码
 watch: {
    visible() {
      if (this.visible) {
        this.innerFullscreen = this.fullscreen
        if(!this.innerFullscreen){
          this.$nextTick(()=>{
            const dialogHeaderEl = document.querySelector('.ant-modal-header')
            const dragDom = document.querySelector('.ant-modal')
            this.cursor =  dragDom.style.cursor
            dialogHeaderEl.style.cursor = 'move'
            dragDom.style.position = 'absolute'
            dragDom.style.marginTop = 0
            let width = dragDom.style.width
            if (width.includes('%')) {
              width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100)
            } else {
              width = +width.replace(/\px/g, '')
            }
            let modalClientWidth = document.getElementsByClassName('ant-modal')[0].clientWidth
            if(width > modalClientWidth && modalClientWidth > 0){
              width = modalClientWidth;
            }
            // 使用window.innerWidth获取视口宽度
            const viewportWidth = window.innerWidth;
            console.log(viewportWidth,width)
            // 考虑到顶部和底部可能存在的系统栏,我们可以减去一个安全的预留值,比如20px
            const leftPosition = ((viewportWidth - width) / 2);
            console.log('leftPosition',leftPosition)
            // 确保left值不会为负数,防止对话框移出屏幕
            dragDom.style.left = Math.max(0, leftPosition) + 'px';
            this.onDrag();
          })
        }
      }
    },
  },
3.4、拖拽方法实现
javascript 复制代码
onDrag(){
      let that = this;
      this.$nextTick(() => {
        const dialogHeaderEl = document.querySelector('.ant-modal-header');
        const dragDom = document.querySelector('.ant-modal');
        const sty = dragDom.style || window.getComputedStyle(dragDom, null)
        const userAgent = navigator.userAgent;
        if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent)) {
          dialogHeaderEl.addEventListener('touchstart', onTouchStart);
          function onTouchStart(e){
            // 记录初始触摸位置
            const target = e.target;
            // 检查点击的是否为全屏按钮
            if (target.classList.contains('ant-modal-close') && target.classList.contains('ant-modal-close-x')) {
              return; // 如果是全屏按钮,则直接返回不做处理
            }
            e.preventDefault();
            const touchStart = e.touches[0];
            const disX = touchStart.clientX - dialogHeaderEl.offsetLeft;
            const disY = touchStart.clientY - dialogHeaderEl.offsetTop;

            // 获取到的值带px 正则匹配替换
            let styL, styT;

            // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
            if (sty.left.includes('%')) {
              styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
              styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
            } else {
              styL = +sty.left.replace(/\px/g, '');
              styT = +sty.top.replace(/\px/g, '');
            }

            // 添加触摸移动事件监听
            document.addEventListener('touchmove', onTouchMove, { passive: false });
            function onTouchMove(e) {
              // 计算移动的距离
              const touchMove = e.touches[0];
              const l = touchMove.clientX - disX;
              const t = touchMove.clientY - disY;

              let finallyL = l + styL;
              let finallyT = t + styT;

              // 检查并防止对话框超出屏幕边界
              const viewportWidth = document.documentElement.clientWidth;
              const viewportHeight = document.documentElement.clientHeight;
              const dialogWidth = dragDom.offsetWidth;
              const dialogHeight = dragDom.querySelector('.ant-modal-content').offsetHeight;
              const safeMargin = that.getStatusBarHeightByUserAgent() + 20;
              finallyL = Math.max(0, finallyL); // 防止向左超出
              finallyL = Math.min(viewportWidth - dialogWidth, finallyL); // 防止向右超出
              finallyT = Math.max(0, finallyT); // 防止向上超出
              finallyT = Math.min(viewportHeight - dialogHeight, finallyT + safeMargin); // 防止向下超出

              // 移动当前元素
              dragDom.style.left = `${finallyL}px`;
              dragDom.style.top = `${finallyT}px`;
            }

            // 添加触摸结束事件监听
            document.addEventListener('touchend', onTouchEnd);
            function onTouchEnd() {
              // 移除事件监听器
              document.removeEventListener('touchmove', onTouchMove);
              document.removeEventListener('touchend', onTouchEnd);
            }
          }
        } else {
          //pc端
          dialogHeaderEl.addEventListener('mousedown', onMousedown);
          function onMousedown(e){
            e.preventDefault();
            // 鼠标按下,计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离)
            const disX = e.clientX - dialogHeaderEl.offsetLeft
            const disY = e.clientY - dialogHeaderEl.offsetTop

            // 获取到的值带px 正则匹配替换
            let styL, styT

            // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
            if (sty.left.includes('%')) {
              styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100)
              styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100)
            } else {
              styL = +sty.left.replace(/\px/g, '')
              styT = +sty.top.replace(/\px/g, '')
            }
            document.addEventListener('mousemove',onMouseMove)
            function  onMouseMove(e){
              // 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离)
              const l = e.clientX - disX;
              const t = e.clientY - disY;

              let finallyL = l + styL;
              let finallyT = t + styT;
              // 检查并防止对话框超出屏幕边界
              const viewportWidth = document.documentElement.clientWidth;
              const viewportHeight = document.documentElement.clientHeight;
              const dialogWidth = dragDom.offsetWidth;
              const dialogHeight =dragDom.querySelector('.ant-modal-content').offsetHeight;


              finallyL = Math.max(0, finallyL); // 防止向左超出
              finallyL = Math.min(viewportWidth - dialogWidth, finallyL); // 防止向右超出
              finallyT = Math.max(0, finallyT); // 防止向上超出
              finallyT = Math.min(viewportHeight - dialogHeight, finallyT); // 防止向下超出

              // 移动当前元素
              dragDom.style.left = `${finallyL}px`;
              dragDom.style.top = `${finallyT}px`;
            }
            document.addEventListener('mouseup',onMouseup)
            function onMouseup(){
              document.removeEventListener('mousemove',onMouseMove);
              document.removeEventListener('mouseup',onMouseup)
            }
          }
        }
      })
    },
相关推荐
提笔了无痕5 小时前
Web中Token验证如何实现(go语言)
前端·go·json·restful
戌中横6 小时前
JavaScript——Web APIs DOM
前端·javascript·html
Beginner x_u6 小时前
如何解释JavaScript 中 this 的值?
开发语言·前端·javascript·this 指针
HWL56796 小时前
获取网页首屏加载时间
前端·javascript·vue.js
烟锁池塘柳07 小时前
【已解决】Google Chrome 浏览器报错 STATUS_ACCESS_VIOLATION 的解决方案
前端·chrome
速易达网络7 小时前
基于RuoYi-Vue 框架美妆系统
前端·javascript·vue.js
LYS_06187 小时前
RM赛事C型板九轴IMU解算(4)(卡尔曼滤波)
c语言·开发语言·前端·卡尔曼滤波
We་ct8 小时前
LeetCode 151. 反转字符串中的单词:两种解法深度剖析
前端·算法·leetcode·typescript
yinmaisoft8 小时前
JNPF 表单模板实操:高效复用表单设计指南
前端·javascript·html
37方寸8 小时前
前端基础知识(JavaScript)
开发语言·前端·javascript