Element-UI中el-dialog弹框在Vue中如何实现浏览器可见区域拖拽功能

发布于:2025-06-19 ⋅ 阅读:(13) ⋅ 点赞:(0)

        Elemenut-UI中会发现el-dialog弹框是没有拖拽功能,而在很多项目中是需要这一功能的;在Vue中,可以使用Vue.directive钩子函数,从底层并通过添加标签属性方式,来实现这一功能。

        在之前一篇文章中,也写过el-dialog弹框的拖拽功能,因通过position定位方式来实现,所以实现效果不算很好;这一篇,将通过transform动画方式完成弹框拖拽移位,并将其限定在浏览器可见区域的拖拽功能。

一、创建页面

        在src/views目录中,创建Dialog/index.vue文件,并在router中添加该页面路径。代码如下:

<template>
  <div style="padding: 30px;">
    <el-button type="primary" size="mini" @click="() => visible = !visible">显示</el-button>
    <el-dialog :visible.sync="visible" width="500px" title="标题">
      <div style="min-height: 200px;">这是一个可拖拽的弹框,并且限定在浏览器可限定区域显示。</div>
    </el-dialog>
  </div>
</template>
<script>
export default {
  data() {
    return {
      visible: false
    }
  }
}
</script>

        页面效果如下:

二、自定义指令

        Vue 2 的自定义指令提供了以下生命周期钩子:

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。
  • update:所在组件的 VNode 更新时调用,但可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

        以下是一个简单的自定义指令示例,用于在元素插入 DOM 后自动获取焦点。

<template>
  <div>
    <input v-focus />
  </div>
</template>

<script>
export default {
  directives: {
    focus: {
      // 当被绑定的元素插入到 DOM 中时…
      inserted(el) {
        // 聚焦元素
        el.focus();
      }
    }
  }
};
</script>

三、全局定义指令

        创建src/directives/index.js文件,定义全局自定义指令。示例代码如下:

import Vue from 'vue'
/**
 * 定义全局 自定义指令
 */
Vue.directive('dragDialog', {
  inserted(el){
    // 初始化样式
  },
  componentUpdated(el){
    // 恢复原位置
  },
  bind(el){
    // 定义绑定事件
  }
})

        此时在main.js引入自定义指令,代码如下:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
// 引入指令
import '@/directives/index'
 
Vue.use(ElementUI);

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

        在el-dialog标签上,添加自定义可拖拽的指令。代码如下:

<template>
  <div style="padding: 30px;">
    <el-button type="primary" size="mini" @click="() => visible = !visible">显示</el-button>
    <el-dialog :visible.sync="visible" width="500px" title="标题" v-dragDialog>
      <div style="min-height: 200px;">这是一个可拖拽的弹框,并且限定在浏览器可限定区域显示。</div>
    </el-dialog>
  </div>
</template>
<script>
export default {
  data() {
    return {
      visible: false
    }
  }
}
</script>

四、实现拖拽功能

        自定义指令可对DOM的操作,实现节点元素的查询、位置的动态调整、事件的定义等。通过自定义指令,可以在 Vue 2 中实现许多强大的功能。

4.1 默认样式

        首先,在inserted中查询el-dialog__header的DOM节点元素,在弹框头部样式中设置鼠标样式、内容禁止选择等。

        示例代码如下:

Vue.directive('dragDialog', {
  inserted(el){
    const header = el.querySelector('.el-dialog__header')
    // 初始化样式
    header.style.cursor = 'pointer'     // 鼠标更换为 小手图标
    header.style.userSelect = 'none'    // 禁止选择内容
  },
  componentUpdated(el){
    // 恢复原位置
  },
  bind(el){
    // 定义绑定事件
  }
})

4.2 绑定事件

        自定义指令bind只调用一次,所以事件的相关初始化操作,全部在这里进行定义。

        首先,使用querySelector()方法查询出弹框(el-dialog)和弹框标题区域(el-dialog)的DOM节点元素,然后通过style的getPropertyValue()属性方法,查询出el-dialog的transform位置信息,并初始化鼠标移动时,所需的position位置信息。 

        在鼠标按下时,添加document监听事件(移动和放开事件),通过mousemove移动事件,实时计算出弹框的位置数据,并重置transform的x,y轴数据。

        当鼠标放开时,移出mousemove和mouseup事件。

        代码如下:

import Vue from 'vue'
/**
 * 定义全局 自定义指令
 */
Vue.directive('dragDialog', {
  inserted(el) {
    const header = el.querySelector('.el-dialog__header')
    const _dialog = el.querySelector('.el-dialog')
    // 初始化样式
    header.style.cursor = 'pointer'     // 鼠标更换为 小手图标
    header.style.userSelect = 'none'    // 禁止选择内容
    _dialog.style.transform = 'translate(0px, 0px)'
  },
  bind(el) {
    // 查询DOM 和 transform
    const _dialog = el.querySelector('.el-dialog')
    const _header = el.querySelector('.el-dialog__header')
    // 初始化
    let _pos = {
      left: 0,
      top: 0,
      x: 0,
      y: 0
    }
    // 设置移动样式
    _header.style.cursor = 'move'
    // 鼠标移动事件的回调函数
    const mousemoveCallback = (e) => {
      // 计算位置
      let dx = e.clientX - _pos.x
      let dy = e.clientY - _pos.y
      // 设置 位置
      _dialog.style.transform = `translate(${dx}px, ${dy}px)`
    }
    // 放开鼠标 事件的回调函数
    const mouseupCallback = () => {
      document.removeEventListener('mousemove', mousemoveCallback)
      document.removeEventListener('mouseup', mouseupCallback)
    }

    // 定义绑定事件
    _header.addEventListener('mousedown', (e) => {
      // 获取transform
      const _transform = _dialog.style.getPropertyValue('transform')
      const _matrix = new DOMMatrix(_transform)
      // 初始位置 
      _pos = {
        left: _matrix.m41,
        top: _matrix.m42,
        x: e.clientX,
        y: e.clientY
      }
      // 添加事件
      document.addEventListener('mousemove', mousemoveCallback) // 移动事件
      document.addEventListener('mouseup', mouseupCallback) // 放开事件
    })
    //  end
  }
})

        以上功能完成后,弹框则可以灵活的被拖拽到浏览器中任何位置。

4.3 限定范围

        如果希望弹框在浏览器可视区域范围内移动,则可以如下图,在mousedown事件点击时,计算出el-dialog弹框四周可移动距离。

        代码如下:

import Vue from 'vue'
/**
 * 定义全局 自定义指令
 */
Vue.directive('dragDialog', {
  inserted(el) {
    const header = el.querySelector('.el-dialog__header')
    const _dialog = el.querySelector('.el-dialog')
    // 初始化样式
    header.style.cursor = 'pointer'     // 鼠标更换为 小手图标
    header.style.userSelect = 'none'    // 禁止选择内容
    _dialog.style.transform = 'translate(0px, 0px)'
  },
  bind(el) {
    // 查询DOM 和 transform
    const _dialog = el.querySelector('.el-dialog')
    const _header = el.querySelector('.el-dialog__header')
    // 初始化
    let _pos = {
      left: 0,
      top: 0,
      x: 0,
      y: 0
    }
    // 设置移动样式
    _header.style.cursor = 'move'
    // 鼠标移动事件的回调函数
    const mousemoveCallback = (e) => {
      // 计算位置
      let dx = e.clientX - _pos.x
      let dy = e.clientY - _pos.y
      // 限定左侧位置
      if (dx <= -_pos.limitX) {
        dx = -_pos.limitX
      }
      // 限定右侧位置 
      else if (dx >= _pos.limitX) {
        dx = _pos.limitX
      }
      // 限定顶部位置 
      if (dy <= -_dialog.offsetTop) {
        dy = -_dialog.offsetTop
      }
      // 限定底部位置 
      else if (dy >= _pos.limitY) {
        dy = _pos.limitY
      }
      // 设置 位置
      _dialog.style.transform = `translate(${dx}px, ${dy}px)`
    }
    // 放开鼠标 事件的回调函数
    const mouseupCallback = () => {
      document.removeEventListener('mousemove', mousemoveCallback)
      document.removeEventListener('mouseup', mouseupCallback)
    }

    // 定义绑定事件
    _header.addEventListener('mousedown', (e) => {
      // 获取transform
      const _transform = _dialog.style.getPropertyValue('transform')
      const _matrix = new DOMMatrix(_transform)
      // 计算边缘
      const limitX = (el.offsetWidth - _dialog.offsetWidth) / 2
      const limitY= el.offsetHeight - _dialog.offsetTop - _dialog.offsetHeight
      // 初始位置 
      _pos = {
        left: _matrix.m41,
        top: _matrix.m42,
        x: e.clientX,
        y: e.clientY,
        limitX: Math.abs(limitX),
        limitY: Math.abs(limitY)
      }
      // 添加事件
      document.addEventListener('mousemove', mousemoveCallback) // 移动事件
      document.addEventListener('mouseup', mouseupCallback) // 放开事件
    })
    // mousedown end
  }
})

        此时,再拖拽弹框,则无法拖出浏览器可视区域了。

4.4 恢复位置

        但是,如果弹框被关闭后,重新打开,则会发现弹框会在上次移动的位置,而没有恢复到浏览器居中位置。那么,则需要在componentUpdated组件完成更新时,判断transform中x,y位置是否为0。代码如下:

import Vue from 'vue'
/**
 * 定义全局 自定义指令
 */
Vue.directive('dragDialog', {
  inserted(el) {
    const header = el.querySelector('.el-dialog__header')
    const _dialog = el.querySelector('.el-dialog')
    // 初始化样式
    header.style.cursor = 'pointer'     // 鼠标更换为 小手图标
    header.style.userSelect = 'none'    // 禁止选择内容
    _dialog.style.transform = 'translate(0px, 0px)'
  },
  componentUpdated(el) {
    // 查询DOM 和 transform
    const _dialog = el.querySelector('.el-dialog')
    const _transform = _dialog.style.getPropertyValue('transform')
    const _matrix = new DOMMatrix(_transform)
    // 恢复原位置
    if (_matrix.m41 != 0 || _matrix.m42 != 0)
      _dialog.style.transform = 'translate(0px, 0px)'
  },
  bind(el) {
    // 略...
  }
})

4.5 Bug修复

        当el-dialog弹框在初始位置时,transform的x,y轴都为0,所以在计算新位置时,是没有问题的。但是,当弹框被移动后,则重新点击移动,位置会移置初始位置。解决这问题很简单,在计算新位置时,加上x,y轴移动后的位置路径即可。

        代码如下:

import Vue from 'vue'
/**
 * 定义全局 自定义指令
 */
Vue.directive('dragDialog', {
  inserted(el) {
    const header = el.querySelector('.el-dialog__header')
    const _dialog = el.querySelector('.el-dialog')
    // 初始化样式
    header.style.cursor = 'pointer'     // 鼠标更换为 小手图标
    header.style.userSelect = 'none'    // 禁止选择内容
    _dialog.style.transform = 'translate(0px, 0px)'
  },
  componentUpdated(el) {
    // 查询DOM 和 transform
    const _dialog = el.querySelector('.el-dialog')
    const _transform = _dialog.style.getPropertyValue('transform')
    const _matrix = new DOMMatrix(_transform)
    // 恢复原位置
    if (_matrix.m41 != 0 || _matrix.m42 != 0)
      _dialog.style.transform = 'translate(0px, 0px)'
  },
  bind(el) {
    // 查询DOM 和 transform
    const _dialog = el.querySelector('.el-dialog')
    const _header = el.querySelector('.el-dialog__header')
    // 初始化
    let _pos = {
      left: 0,
      top: 0,
      x: 0,
      y: 0
    }
    // 设置移动样式
    _header.style.cursor = 'move'
    // 鼠标移动事件的回调函数
    const mousemoveCallback = (e) => {
      // 计算位置
      let dx = e.clientX - _pos.x + _pos.left
      let dy = e.clientY - _pos.y + _pos.top
      // 限定左侧位置
      if (dx <= -_pos.limitX) {
        dx = -_pos.limitX
      }
      // 限定右侧位置 
      else if (dx >= _pos.limitX) {
        dx = _pos.limitX
      }
      // 限定顶部位置 
      if (dy <= -_dialog.offsetTop) {
        dy = -_dialog.offsetTop
      }
      // 限定底部位置 
      else if (dy >= _pos.limitY) {
        dy = _pos.limitY
      }
      // 设置 位置
      _dialog.style.transform = `translate(${dx}px, ${dy}px)`
    }
    // 放开鼠标 事件的回调函数
    const mouseupCallback = () => {
      document.removeEventListener('mousemove', mousemoveCallback)
      document.removeEventListener('mouseup', mouseupCallback)
    }

    // 定义绑定事件
    _header.addEventListener('mousedown', (e) => {
      // 获取transform
      const _transform = _dialog.style.getPropertyValue('transform')
      const _matrix = new DOMMatrix(_transform)
      // 计算边缘
      const limitX = (el.offsetWidth - _dialog.offsetWidth) / 2
      const limitY = el.offsetHeight - _dialog.offsetTop - _dialog.offsetHeight
      // 初始位置 
      _pos = {
        left: _matrix.m41,
        top: _matrix.m42,
        x: e.clientX,
        y: e.clientY,
        limitX: Math.abs(limitX),
        limitY: Math.abs(limitY)
      }
      // 添加事件
      document.addEventListener('mousemove', mousemoveCallback) // 移动事件
      document.addEventListener('mouseup', mouseupCallback) // 放开事件
    })
    // mousedown end
  }
})

        在vue中,增加el-dialog可拖拽功能,就已完成了。


网站公告

今日签到

点亮在社区的每一天
去签到