使用vue3的自定义指令给 el-dialog 增加拖拽功能

简介: element-plus 提供的 el-dialog 对话框功能非常强大,只是美中不足不能通过拖拽的方式改变位置,有点小遗憾,那么怎么办呢?我们可以通过 vue 的自定义指令来实现一个可以拖拽的对话框(el-dialog)。

先看看拖拽效果



www.zhihu.com/zvideo/1380…


vue 的自定义指令 directive



为啥选择自定义指令的方式来实现呢?一个是可以方便的获得 dom 便于操作,另一个是方便使用和封装。


自定义指令有两种注册方式,一个是全局注册,一个是局部注册。


  • 全局注册自定义指令


app.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  mounted(el) {
    // Focus the element
    el.focus()
  }
})
复制代码


来自于官网。后面做插件的时候需要用到。


  • 局部注册自定义指令


directives: {
  focus: {
    // 指令的定义
    mounted(el) {
      el.focus()
    }
  }
}
复制代码


在开发测试的阶段可以用这种方式,便于调试。插件每次修改的时候都会重新加载,而局部注册的只需要局部更新即可。


我们可以定义一个 dialogdrag,然后在 mounted 里面实现拖拽的功能。


分析 element-plus 的 Dialog 对话框



想要实现拖拽功能,首先要了解 Dialog 对话框渲染出来的结构,然后才好针对性下手改造。 通过分析可见如下结构:


image.png


简单的说,一个 div 里面放了三个 div,通过 margin(top、left) 来实现“居中”的效果。 那么也就是说我们想要实现拖拽功能的话,可以通过改变 margin-left、margin-top 的方式来。


鼠标的三个函数



提到拖拽功能,那么鼠标的三个事件 onmousedown、onmousemove、onmouseup 就必不可少了。


  • onmousedown


鼠标按下的时候记录光标的坐标,进入拖拽状态。


  • onmouseup


鼠标抬起的时候记录光标的坐标,结束拖拽状态。


  • onmousemove


按住鼠标拖动的时候触发,计算光标的偏移量,修改对话框的 margin 实现拖拽的效果。


实现代码



本来想写一个通用一点的,但是对话框渲染出来的结构比较复杂,似乎也不够通用,所以先针对 el-dialog 实现拖拽功能。


app.directive('dialogdrag', {
    // 渲染完毕
    mounted(el, binding) {
      // binding.arg
      // binding.value
      // 可视窗口的宽度
      const clientWidth = document.documentElement.clientWidth
      // 可视窗口的高度
      const clientHeight = document.documentElement.clientHeight
      // 记录坐标
      let domset = {
        x: clientWidth / 4, // 默认width 50%
        y: clientHeight * 15 / 100  // 根据 15vh 计算
      }
      // 弹窗的容器
      const domDrag = el.firstElementChild.firstElementChild
      // 重新设置上、左距离
      domDrag.style.marginTop = domset.y + 'px'
      domDrag.style.marginLeft = domset.x + 'px'
      // 记录拖拽开始的光标坐标,0 表示没有拖拽
      let start = { x: 0, y: 0 }
      // 移动中记录偏移量
      let move = { x: 0, y: 0 }
      // 鼠标按下,开始拖拽
      domDrag.onmousedown = (e) => {
        // 判断对话框是否重新打开
        if (domDrag.style.marginTop === '15vh') {
          // 重新打开,设置 domset.y  top
          domset.y = clientHeight * 15 / 100
        }
        start.x = e.clientX
        start.y = e.clientY
        domDrag.style.cursor = 'move' // 改变光标形状
      }
      // 鼠标移动,实时跟踪
      domDrag.onmousemove = (e) => {
        if (start.x === 0) { // 不是拖拽状态
          return
        }
        move.x = e.clientX - start.x
        move.y = e.clientY - start.y
        // 初始位置 + 拖拽距离
        domDrag.style.marginLeft = ( domset.x + move.x )  + 'px'
        domDrag.style.marginTop = ( domset.y + move.y )  + 'px'
      }
      // 鼠标抬起,结束拖拽
      domDrag.onmouseup = (e) => {
        move.x = e.clientX - start.x
        move.y = e.clientY - start.y
        // 记录新坐标,作为下次拖拽的初始位置
        domset.x += move.x
        domset.y += move.y
        domDrag.style.cursor = '' // 恢复光标形状
        domDrag.style.marginLeft = domset.x + 'px'
        domDrag.style.marginTop = domset.y + 'px'
        // 结束拖拽
        start.x = 0
      }
    }
  })
复制代码


  • 重新定位对话框


默认的 top 是15vh,也就是距离顶部 15% ,所以需要 用 clientHeight * 15 / 100 转换为像素。 默认的 left 是 50%,所以需要用 clientWidth / 4 转换为像素。


这样修改有一个小问题,当窗口大小发生改变的时候,左距离不会随之改变。


  • 记录位置坐标和偏移量


首先要记录对话框的距离,然后要记录拖拽的时候产生的偏移量。


  1. domset 可以记录对话框的初始坐标。
  2. start 可以记录开始拖拽的时候光标的位置。
  3. move 记录拖拽过程中,光标移动的偏移量。


  • 按下鼠标 onmousedown


按下鼠标,表示开始拖拽,这时候需要我们记录光标的位置。 另外在测试的时候发现一个小问题,当关闭对话框的时候有一个过渡动画,然后在打开对话框进行拖拽的时候,就飞掉了。


找了一下原因后发现,在关闭的过渡动画的时候,会把 top 改成 15vh,这样就和我们拖拽后的 top 不一致。


所以在按下鼠标的时候需要做一个判断。如果恢复了初始状态,那么需要改一下 domset 的 y 坐标。x坐标不用改,因为过渡动画没有改 left 。


  • 移动鼠标 onmousemove


在移动鼠标的过程中,我们可以得到光标的位置,减去初始光标位置,就是对话框要移动的距离。


然后我们用对话框的 初始坐标 + 偏移量,就可以得到对话框的新的位置坐标。 这样就实现了对话框的拖拽。


  • 抬起鼠标 onmouseup


不能一直拖拽,所以我们需要一个结束动作。


当抬起鼠标的时候,我们可以认为是结束拖拽了,这时我们要记录对话框的新的位置坐标, 然后设置 start.x = 0 表示结束拖拽。


做成插件便于复用



最后我们把这个拖拽功能做成一个插件,这样可以便于全局注册。


建立一个js文件


// dialogDrag.js
const dialogDrag = (app, options) => {
  app.directive('dialogdrag', {
    // 指令的定义
    mounted(el, binding) {
    同上,略...
}
export default dialogDrag
复制代码


然后在 main.js 里面挂载这个插件。


// 拖拽
import dialogDrag from './control-web/js/dialogDrag.js'
createApp(App).use(dialogDrag) // 对话框的拖拽
复制代码


使用方式



本来想直接放在 el-dialog 里面,但是却没有效果,所以只好在外面套上一个 div。


<div v-dialogdrag>
    <el-dialog
      title="收货地址"
      v-model="dialogFormVisible"
      :modal="false"
    >
     略...
    </el-dialog>
  </div>
复制代码


注意,要加上 v- ,即 v-dialogdrag。


源码


gitee.com/naturefw/nf…



相关文章
|
22天前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
126 64
|
22天前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
28 8
|
21天前
|
存储 JavaScript 数据管理
除了provide/inject,Vue3中还有哪些方式可以避免v-model的循环引用?
需要注意的是,在实际开发中,应根据具体的项目需求和组件结构来选择合适的方式来避免`v-model`的循环引用。同时,要综合考虑代码的可读性、可维护性和性能等因素,以确保系统的稳定和高效运行。
21 1
|
21天前
|
JavaScript
Vue3中使用provide/inject来避免v-model的循环引用
`provide`和`inject`是 Vue 3 中非常有用的特性,在处理一些复杂的组件间通信问题时,可以提供一种灵活的解决方案。通过合理使用它们,可以帮助我们更好地避免`v-model`的循环引用问题,提高代码的质量和可维护性。
32 1
|
22天前
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
|
JavaScript 数据安全/隐私保护
Vue:几个实用的 Vue 自定义指令
Vue:几个实用的 Vue 自定义指令
115 0
Vue:几个实用的 Vue 自定义指令
|
25天前
|
JavaScript API 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
27天前
|
JavaScript 前端开发 开发者
vue 数据驱动视图
总之,Vue 数据驱动视图是一种先进的理念和技术,它为前端开发带来了巨大的便利和优势。通过理解和应用这一特性,开发者能够构建出更加动态、高效、用户体验良好的前端应用。在不断发展的前端领域中,数据驱动视图将继续发挥重要作用,推动着应用界面的不断创新和进化。
|
1天前
|
JavaScript 关系型数据库 MySQL
基于VUE的校园二手交易平台系统设计与实现毕业设计论文模板
基于Vue的校园二手交易平台是一款专为校园用户设计的在线交易系统,提供简洁高效、安全可靠的二手商品买卖环境。平台利用Vue框架的响应式数据绑定和组件化特性,实现用户友好的界面,方便商品浏览、发布与管理。该系统采用Node.js、MySQL及B/S架构,确保稳定性和多功能模块设计,涵盖管理员和用户功能模块,促进物品循环使用,降低开销,提升环保意识,助力绿色校园文化建设。
|
29天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
32 1
vue学习第一章
下一篇
DataWorks