基于 el-dialog 实现全屏与拖拽功能的自定义指令(全网最详细教程)

一、功能概述

通过 Vue 自定义指令方式为 Element UI 的 el-dialog 组件添加:

  1. 点击头部全屏按钮切换全屏/恢复

  2. 拖拽头部移动对话框

  3. 全屏按钮控制

 

二、实现原理

2.1 技术基础

  • Vue 自定义指令:通过 directive 方法创建可复用指令

  • DOM 操作:动态修改对话框样式属性

  • 事件监听:mousedown/mousemove/mouseup 实现拖拽逻辑

 

三、代码实现(分步骤详解)

3.1 创建指令文件

新建 full.js 文件, 核心代码:

// 全屏处理函数
function handleFullScreen(dialog, isFullScreen, marginTop, width) {
  // ...(见原始代码)
}

const fullScreenDirective = Vue => {
  Vue.directive('fullScreen', {
    bind(el, binding, vnode) {
      // 核心实现逻辑
    }
  })
}

export default fullScreenDirective

 

3.2 注册指令

在 main.js 中全局注册:

import fullScreenDirective from '@/directive/full.js';
Vue.use(fullScreenDirective)

3.3 拖拽功能实现

3.3.1 获取对话框元素
const dialogHeaderEl = el.querySelector('.el-dialog__header')
const dragDom = el.querySelector('.el-dialog')
3.3.2 鼠标按下事件
dialogHeaderEl.onmousedown = (e) => {
  // 计算初始偏移量
  const disX = e.clientX - dialogHeaderEl.offsetLeft
  const disY = e.clientY - dialogHeaderEl.offsetTop

  // 获取屏幕尺寸
  const screenWidth = document.body.clientWidth
  const screenHeight = document.body.clientHeight

  // 边界处理参数
  const minDragDomLeft = dragDom.offsetLeft
  const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDom.offsetWidth
}
3.3.3 鼠标移动处理
document.onmousemove = function (e) {
  // 计算新位置
  let left = e.clientX - disX
  let top = e.clientY - disY

  // 边界限制
  left = Math.max(-minDragDomLeft, Math.min(left, maxDragDomLeft))
  top = Math.max(-minDragDomTop, Math.min(top, maxDragDomTop))

  // 更新位置
  dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
}

3.4 全屏功能实现

3.4.1 样式保存与恢复
function handleFullScreen(dialog, isFullScreen, marginTop, width) {
  if (!dialog) return false;

  // 保存原始样式
  if (!dialog.__originalStyle) {
    dialog.__originalStyle = {
      margin: dialog.style.margin,
      padding: dialog.style.padding,
      boxSizing: dialog.style.boxSizing,
      position: dialog.style.position,
      top: dialog.style.top,
      left: dialog.style.left,
      width: dialog.style.width,
      height: dialog.style.height
    };
  }

  if (isFullScreen) {
    // 保存当前可视位置
    dialog.__scrollTop = document.documentElement.scrollTop;

    Object.assign(dialog.style, {
      margin: '0',
      padding: '0',
      boxSizing: 'border-box',
      position: 'fixed',
      top: '-56px',
      left: '258px',
      width: 'calc(100% - 258px)',
      height: '100vh',
      zIndex: 9999
    });
  } else {
    // 还原原始样式
    Object.assign(dialog.style, dialog.__originalStyle);

    // 恢复滚动位置
    if (dialog.__scrollTop !== undefined) {
      document.documentElement.scrollTop = dialog.__scrollTop;
    }
  }
}
3.4.2 创建全屏按钮
const fullScreenBtn = document.createElement('button')
fullScreenBtn.innerHTML = ''
dialogHeaderEl.append(fullScreenBtn)

// 双击事件
dialogHeaderEl.addEventListener('dblclick', toggleFullScreen)

3.5 完整指令代码




function handleFullScreen(dialog, isFullScreen, marginTop, width) {
  if (!dialog) return false;

  // 保存原始样式
  if (!dialog.__originalStyle) {
    dialog.__originalStyle = {
      margin: dialog.style.margin,
      padding: dialog.style.padding,
      boxSizing: dialog.style.boxSizing,
      position: dialog.style.position,
      top: dialog.style.top,
      left: dialog.style.left,
      width: dialog.style.width,
      height: dialog.style.height
    };
  }

  if (isFullScreen) {
    // 保存当前可视位置
    dialog.__scrollTop = document.documentElement.scrollTop;

    Object.assign(dialog.style, {
      margin: '0',
      padding: '0',
      boxSizing: 'border-box',
      position: 'fixed',
      top: '-56px',
      left: '258px',
      width: 'calc(100% - 258px)',
      height: '100vh',
      zIndex: 9999
    });
  } else {
    // 还原原始样式
    Object.assign(dialog.style, dialog.__originalStyle);

    // 恢复滚动位置
    if (dialog.__scrollTop !== undefined) {
      document.documentElement.scrollTop = dialog.__scrollTop;
    }
  }
}

const fullScreenDirective = Vue => {

  Vue.directive('fullScreen', { // 属性名称draggable-dialog,前面加v- 使用
    bind(el, binding, vnode) {
      const dialogHeaderEl = el.querySelector('.el-dialog__header')
      const dragDom = el.querySelector('.el-dialog')
      dialogHeaderEl.style.cssText += ';cursor:move;'
      dragDom.style.cssText += ';top:0px;'



      // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
      const getStyle = (function () {
        if (window.document.currentStyle) {
          return (dom, attr) => dom.currentStyle[attr]
        } else {
          return (dom, attr) => getComputedStyle(dom, false)[attr]
        }
      })()

      dialogHeaderEl.onmousedown = (e) => {
        // 鼠标按下,计算当前元素距离可视区的距离
        const disX = e.clientX - dialogHeaderEl.offsetLeft
        const disY = e.clientY - dialogHeaderEl.offsetTop

        const dragDomWidth = dragDom.offsetWidth
        // const dragDomHeight = dragDom.offsetHeight

        const screenWidth = document.body.clientWidth
        const screenHeight = document.body.clientHeight

        const minDragDomLeft = dragDom.offsetLeft
        const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth

        const minDragDomTop = dragDom.offsetTop
        // const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
        const maxDragDomTop = screenHeight - dragDom.offsetTop// 不需要-dragDomHeight

        // 获取到的值带px 正则匹配替换
        let styL = getStyle(dragDom, 'left')
        let styT = getStyle(dragDom, 'top')

        if (styL.includes('%')) {
          styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
          styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
        } else {
          styL = +styL.replace(/\px/g, '')
          styT = +styT.replace(/\px/g, '')
        }

        document.onmousemove = function (e) {
          // 通过事件委托,计算移动的距离
          let left = e.clientX - disX
          let top = e.clientY - disY

          // 边界处理
          if (-(left) > minDragDomLeft) {
            left = -minDragDomLeft
          } else if (left > maxDragDomLeft) {
            left = maxDragDomLeft
          }

          if (-(top) > minDragDomTop) {
            top = -minDragDomTop
          } else if (top > maxDragDomTop) {
            top = maxDragDomTop
          }

          // 移动当前元素
          dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`

          // emit onDrag event
          vnode.child.$emit('draggable-dialog')
        }

        document.onmouseup = function (e) {
          document.onmousemove = null
          document.onmouseup = null
        }
      }



      let isFullScreen = false

      const header = el.querySelector('.el-dialog__header')
      if (!dialogHeaderEl || !dragDom || !vnode) return
      dialogHeaderEl.style.paddingRight = '43px'
      dialogHeaderEl.style.boxShadow = '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)'
      const { width, top } = vnode.componentInstance

      const fullScreenBtn = document.createElement('button')
      fullScreenBtn.type = 'button'
      fullScreenBtn.style = 'float:right;padding:0;background: 0 0;border:0;outline:0;cursor:pointer;font-size:16px;'

      const fullScreenIcon = document.createElement('i')
      fullScreenIcon.className = 'el-icon el-icon-full-screen'
      fullScreenBtn.append(fullScreenIcon)

      fullScreenBtn.addEventListener('click', () => {
        isFullScreen = !isFullScreen
        handleFullScreen(dragDom, isFullScreen, top, width)
        return false
      })

      dialogHeaderEl.append(fullScreenBtn)

      dialogHeaderEl.addEventListener('dblclick', () => {
        isFullScreen = !isFullScreen
        handleFullScreen(dragDom, isFullScreen, top, width)
        return false
      })
    }
  })
}

export default fullScreenDirective

四、使用方式

五、核心实现细节

5.1 样式处理要点

  • 全屏时使用 position: fixed 脱离文档流

  • 计算 calc(100% - 258px) 适应侧边栏布局: 排除菜单的宽度,这里可以改成动态获取

  • 保存原始样式实现完美还原

5.2 拖拽边界处理

// 横向边界
left = Math.max(-minDragDomLeft, Math.min(left, maxDragDomLeft))

// 纵向边界 
top = Math.max(-minDragDomTop, Math.min(top, maxDragDomTop))

5.3 事件处理优化

  • 使用事件委托提升性能

  • 及时移除事件监听防止内存泄漏

document.onmouseup = function() {
  document.onmousemove = null
  document.onmouseup = null
}

六、注意事项

  1. z-index 层级:全屏时设置为 9999 确保置顶

  2. 滚动位置保存:记录 document.documentElement.scrollTop

  3. 百分比单位处理:转换为实际像素值

if (styL.includes('%')) {
  styL = +document.body.clientWidth * (+styL.replace(/%/g, '') / 100)
}

七、扩展能力

  1. 自定义全屏按钮样式:通过 CSS 修改图标

  2. 限制拖拽范围:修改边界计算参数

  3. 动画效果:添加过渡动画提升体验

八、最终效果

 后续补齐..

你可能感兴趣的:(ElementUI,vue.js,前端,javascript,elementui)