el-dialog的放大、缩小、全屏、拖拽功能

该文章已生成可运行项目,

我们在vue3中封装一个自定义指令

import { nextTick } from 'vue';

/**
 * Element Plus 对话框拖拽指令
 * @param {HTMLElement} el - 指令绑定的元素
 * @param {Object} binding - 指令绑定对象
 */
export default {
  updated(el, binding, vnode, oldVnode) {
    // 初始化样式
    el.style.height = 'auto';
    el.style.display = 'block';
    
    // 弹框可拉伸最小宽高
    const minWidth = 30;
    const minHeight = 30;
    
    // 状态变量
    let isFullScreen = false;
    let nowWidth = 0;
    let nowHight = 0;
    let nowMarginTop = 0;
    
    // 获取弹框元素
    const dialogHeaderEl = el.querySelector('.el-dialog__header');
    const dragDom = el.querySelector('.el-dialog');
    
    if (!dragDom) {
      el.style.height = '0px';
      el.style.display = 'none';
      return;
    }

    // 确保对话框使用绝对定位以便拖拽
    if (dragDom.style.position !== 'absolute') {
      dragDom.style.position = 'absolute';
      
      nextTick(()=>{
        // 计算屏幕中心位置
        const screenWidth = window.innerWidth;
        const screenHeight = window.innerHeight;
        const dialogWidth = dragDom.offsetWidth;
        const dialogHeight = dragDom.offsetHeight;
        
        // 设置初始位置为屏幕中央
        const left = (screenWidth - dialogWidth) / 2;
        const top = (screenHeight - dialogHeight) / 2;
        
        dragDom.style.left = left + 'px';
        dragDom.style.top = top + 'px';
      })
    }
    
    
    // 给弹窗加上overflow auto
    dragDom.style.overflow = "auto";

    // 消除外边距
    dragDom.style.marginTop = '0px'
    dragDom.style.marginBottom = '0px'
    dragDom.style.marginLeft = '0px'
    dragDom.style.marginRight = '0px'
    
    // 清除选择头部文字效果
    dialogHeaderEl.onselectstart = () => false;
    
    // 头部加上可拖动cursor
    dialogHeaderEl.style.cursor = 'move';

    // 获取原有样式
    const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);

    // 移动功能
    const moveDown = (e) => {
      const disX = e.clientX - dialogHeaderEl.offsetLeft;
      const disY = e.clientY - dialogHeaderEl.offsetTop;

      let styL, styT;

      // 处理百分比和像素单位
      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, '');
      }

      const onMouseMove = function (e) {
        const l = e.clientX - disX;
        const t = e.clientY - disY;

        // 边界检查
        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;
        const dialogRect = dragDom.getBoundingClientRect();
        
        const boundedLeft = Math.max(0, Math.min(l + styL, viewportWidth - dialogRect.width));
        const boundedTop = Math.max(0, Math.min(t + styT, viewportHeight - dialogRect.height));
        
        dragDom.style.left = boundedLeft + 'px';
        dragDom.style.top = boundedTop + 'px';
      };

      const onMouseUp = function () {
        document.removeEventListener('mousemove', onMouseMove);
        document.removeEventListener('mouseup', onMouseUp);
      };

      document.addEventListener('mousemove', onMouseMove);
      document.addEventListener('mouseup', onMouseUp);
    };

    // 绑定移动事件
    dialogHeaderEl.onmousedown = moveDown;

    // 双击头部全屏/恢复功能
    dialogHeaderEl.ondblclick = () => {
      if (!isFullScreen) {
        // 进入全屏
        nowHight = dragDom.clientHeight;
        nowWidth = dragDom.clientWidth;
        nowMarginTop = dragDom.style.marginTop;
        
        dragDom.style.left = '0px';
        dragDom.style.top = '0px';
        dragDom.style.height = "100vh";
        dragDom.style.width = "100vw";
        dragDom.style.marginTop = '0px';
        isFullScreen = true;
        dialogHeaderEl.style.cursor = 'initial';
        dialogHeaderEl.onmousedown = null;
      } else {
        // 退出全屏
        dragDom.style.height = "auto";
        dragDom.style.width = nowWidth + 'px';
        dragDom.style.marginTop = nowMarginTop;
        isFullScreen = false;
        dialogHeaderEl.style.cursor = 'move';
        dialogHeaderEl.onmousedown = moveDown;
      }
    };

    // 创建八个方向的拖拽控制点
    createAllResizeHandles(dragDom, minWidth, minHeight);
  },

  mounted(el) {
    // 初始化隐藏
    el.style.height = '0px';
    el.style.display = 'none';
    el.style.marginTop = '0px';
    el.style.marginBottom = '0px';
  }
};

/**
 * 创建所有八个方向的拖拽控制点
 */
function createAllResizeHandles(dragDom, minWidth, minHeight) {
  // 定义八个方向及其样式
  const handles = [
    { direction: 'top', cursor: 'n-resize', styles: { height: '10px', width: '100%', top: '0px', left: '0px' }},
    { direction: 'bottom', cursor: 's-resize', styles: { height: '10px', width: '100%', bottom: '0px', left: '0px' }},
    { direction: 'left', cursor: 'w-resize', styles: { height: '100%', width: '10px', top: '0px', left: '0px' }},
    { direction: 'right', cursor: 'e-resize', styles: { height: '100%', width: '10px', top: '0px', right: '0px' }},
    { direction: 'top-left', cursor: 'nw-resize', styles: { height: '10px', width: '10px', top: '0px', left: '0px' }},
    { direction: 'top-right', cursor: 'ne-resize', styles: { height: '10px', width: '10px', top: '0px', right: '0px' }},
    { direction: 'bottom-left', cursor: 'sw-resize', styles: { height: '10px', width: '10px', bottom: '0px', left: '0px' }},
    { direction: 'bottom-right', cursor: 'se-resize', styles: { height: '10px', width: '10px', bottom: '0px', right: '0px' }}
  ];

  // 创建控制点
  handles.forEach(config => {
    createResizeHandle(dragDom, config, minWidth, minHeight);
  });
}

/**
 * 创建单个拖拽控制点
 */
function createResizeHandle(dragDom, config, minWidth, minHeight) {
  const handle = document.createElement("div");
  dragDom.appendChild(handle);
  
  // 应用基本样式
  Object.assign(handle.style, {
    position: 'absolute',
    zIndex: '9999',
    cursor: config.cursor
  }, config.styles);

  // 绑定拖拽事件
  handle.onmousedown = (e) => {
    e.preventDefault();
    e.stopPropagation();
    
    const startX = e.clientX;
    const startY = e.clientY;
    const startWidth = dragDom.clientWidth;
    const startHeight = dragDom.clientHeight;
    const startLeft = dragDom.offsetLeft;
    const startTop = dragDom.offsetTop;

    const onMouseMove = function (e) {
      e.preventDefault();
      
      const deltaX = e.clientX - startX;
      const deltaY = e.clientY - startY;
      
      // 根据方向计算新位置和尺寸
      calculateNewSizeAndPosition(
        dragDom, 
        config.direction, 
        deltaX, 
        deltaY, 
        startWidth, 
        startHeight, 
        startLeft, 
        startTop, 
        minWidth, 
        minHeight
      );
    };

    const onMouseUp = function () {
      document.removeEventListener('mousemove', onMouseMove);
      document.removeEventListener('mouseup', onMouseUp);
    };

    document.addEventListener('mousemove', onMouseMove);
    document.addEventListener('mouseup', onMouseUp);
  };
}

/**
 * 根据拖拽方向和距离计算新位置和尺寸
 */
function calculateNewSizeAndPosition(
  dragDom, 
  direction, 
  deltaX, 
  deltaY, 
  startWidth, 
  startHeight, 
  startLeft, 
  startTop, 
  minWidth, 
  minHeight
) {
  let newWidth = startWidth;
  let newHeight = startHeight;
  let newLeft = startLeft;
  let newTop = startTop;

  switch (direction) {
    case 'top':
      newHeight = Math.max(minHeight, startHeight - deltaY);
      newTop = startTop + deltaY;
      break;
    
    case 'bottom':
      newHeight = Math.max(minHeight, startHeight + deltaY);
      break;
    
    case 'left':
      newWidth = Math.max(minWidth, startWidth - deltaX);
      newLeft = startLeft + deltaX;
      break;
    
    case 'right':
      newWidth = Math.max(minWidth, startWidth + deltaX);
      break;
    
    case 'top-left':
      newWidth = Math.max(minWidth, startWidth - deltaX);
      newHeight = Math.max(minHeight, startHeight - deltaY);
      newLeft = startLeft + deltaX;
      newTop = startTop + deltaY;
      break;
    
    case 'top-right':
      newWidth = Math.max(minWidth, startWidth + deltaX);
      newHeight = Math.max(minHeight, startHeight - deltaY);
      newTop = startTop + deltaY;
      break;
    
    case 'bottom-left':
      newWidth = Math.max(minWidth, startWidth - deltaX);
      newHeight = Math.max(minHeight, startHeight + deltaY);
      newLeft = startLeft + deltaX;
      break;
    
    case 'bottom-right':
      newWidth = Math.max(minWidth, startWidth + deltaX);
      newHeight = Math.max(minHeight, startHeight + deltaY);
      break;
    
    default:
      break;
  }

  // 应用新尺寸和位置
  dragDom.style.width = newWidth + 'px';
  dragDom.style.height = newHeight + 'px';
  dragDom.style.left = newLeft + 'px';
  dragDom.style.top = newTop + 'px';
}

因为指令需要绑定在组件的根元素上,而elementplus的el-dialog有多个根元素,所以我们绑定的指令一般是无效的。所以我们需要在el-dialog外面套一层div,一个el-dialog外面对应一个div。

​
<div v-dialog-drag>
<el-dialog v-model="dialogVisible">
</el-dialog>
</div>

​

在index.ts中声明自定义指令。

import { createApp } from "vue";
import VDialogDrag from "./components/VDialogDrag";

const app = createApp(App);
app.directive("dialog-drag", VDialogDrag)

本文章已经生成可运行项目
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值