实现el-dialog的拖拽功能

这里指的是 element-plus 的el-dialog组件,一开始该组件并没有实现拖拽的功能,当然现在可以通过设置属性的方式实现拖拽。

自带的拖拽功能非常严谨,拖拽时判断是否拖拽出窗口,如果出去了会阻止拖拽。

如果自带的拖拽功能可以满足需求的话,可以跳过本文。

通过自定义指令实现拖拽功能

因为要自己操作dom(设置事件),所以感觉还是使用自定义指令更直接一些,而且对原生组件的影响更小。

我们先定义一个自定义指令 _dialogDrag:

import dialogDrag from './_dialog-drag'
import { watch } from 'vue'
const _dialogDrag = {
  // mounted 
  mounted (el: any, binding: any) {
    // 监听 dialog 是否显示的状态
    watch (binding.value, () => {
      // dialog 不可见,退出
      if (!binding.value.visible) return
      // 寻找 el-dialog 组件
      const container = el.firstElementChild.firstElementChild
      // 已经设置拖拽事件,退出
      if (container.onmousemove) return
      // 等待 DOM 渲染完毕
      setTimeout(() => {
        // 拖拽的 “句柄”
        const _dialogTitle = el.getElementsByClassName('el-dialog__header')
        if (_dialogTitle.length === 0) {
          // 还没有渲染完毕,或则其他原因
          console.warn('没有找到要拖拽的 el-dialog', el)
        } else {
          const { setDialog } = dialogDrag()
          const dialogTitle = _dialogTitle[0]
          // 弹窗
          const dialog = el.firstElementChild.firstElementChild.firstElementChild
          // 通过 css 寻找 el-dialog 设置的宽度
          const arr = dialog.style.cssText.split(';')
          const width = arr[0].replace('%', '').replace('--el-dialog-width:', '') //
          // 设置 el-dialog 组件、弹窗、句柄、宽度
          setDialog(container, dialog, dialogTitle, width)
        }
      },300)
    })
  },
}
/**
 * 注册拖拽 dialog 的自定义指令
 * @param app 
 * @param options 
 */
const install = (app: any, options: any) => {
  app.directive('dialogDrag', _dialogDrag)
}
export {
  _dialogDrag,
  install
}

这里有两个比较烦人的地方:

  • DOM渲染完毕的时机。执行 mounted 的时候,DOM不一定渲染完毕,如果不使用 setTimeout 的话,就会找不到DOM,所以用了这种笨办法。
  • dialog 的隐藏。一般情况下,el-dialog 初始是隐藏状态,隐藏了就意味着DOM并不会被渲染出来。可是自定义指令会在一开始即被执行,这时 setTimeout 的等待时间再长也无用,所以只好监听dialog的状态。
  • ref 通过 template 传递后,再次传入组件的话,就会失去ref的那一层的响应性,所以只能传入reactive才行,这样调用指令的组件,就会比较别扭,目前没有想到更好的实现方式。

实现拖拽功能

定义指令和实现拖拽,我分成了两个文件,我想,尽量解耦一下。

定义一个拖拽函数(dialogDrag):

/**
 * 拖拽 dialog 的函数,目前支持 element-plus
 */
export default function dialogDrag () {
  /**
   * 设置拖拽事件
   * @param container 大容器,比如蒙版。
   * @param dialog 被拖拽的窗口
   * @param dialogTitle 拖拽的标题
   * @param width 宽度比例
   */
  const setDialog = (container: any, dialog: any, dialogTitle: any, width: number) => {
    const oldCursor = dialogTitle.style.cursor
    // 可视窗口的宽度
    const clientWidth = document.documentElement.clientWidth
    // 可视窗口的高度
    const clientHeight = document.documentElement.clientHeight
    // 根据百分数计算宽度
    const tmpWidth = clientWidth * (100 - width) / 200
    // 默认宽度和高度
    const domset = {
      x: tmpWidth,
      y: clientHeight * 15 / 100 // 根据 15vh 计算
    }
    // 查看dialog 当前的宽度和高低
    if (dialog.style.marginLeft === '') {
      dialog.style.marginLeft = domset.x   'px'
    } else {
      domset.x = dialog.style.marginLeft.replace('px','') * 1
    }
    if (dialog.style.marginTop === '') {
      dialog.style.marginTop = domset.y   'px'
    } else {
      domset.y = dialog.style.marginTop.replace('px','') * 1
    }
    // 记录拖拽开始的光标坐标,0 表示没有拖拽
    const start = { x: 0, y: 0 }
    // 移动中记录偏移量
    const move = { x: 0, y: 0 }
    // 经过时改变鼠标指针形状
    dialogTitle.onmouseover = () => {
      dialogTitle.style.cursor = 'move' // 改变光标形状
    }
    // 鼠标按下,开始拖拽
    dialogTitle.onmousedown = (e: any) => {
      start.x = e.clientX
      start.y = e.clientY
      dialogTitle.style.cursor = 'move' // 改变光标形状
    }
    // 鼠标移动,实时跟踪 dialog
    container.onmousemove = (e: any) => {
      if (start.x === 0) { // 不是拖拽状态
        return
      }
      move.x = e.clientX - start.x
      move.y = e.clientY - start.y
      // 初始位置   拖拽距离
      dialog.style.marginLeft = (domset.x   move.x)   'px'
      dialog.style.marginTop = (domset.y   move.y)   'px'
    }
    // 鼠标抬起,结束拖拽
    container.onmouseup = (e: any) => {
      if (start.x === 0) { // 不是拖拽状态
        return
      }
      move.x = e.clientX - start.x
      move.y = e.clientY - start.y
      // 记录新坐标,作为下次拖拽的初始位置
      domset.x  = move.x
      domset.y  = move.y
      dialogTitle.style.cursor = oldCursor
      dialog.style.marginLeft = domset.x   'px'
      dialog.style.marginTop = domset.y   'px'
      // 结束拖拽
      start.x = 0
    }
  }
  return {
    setDialog // 设置
  }
}

首先观察el-dialog渲染后的DOM结构,发现是通过 marginLeft、marginTop 这两个css 的属性,那么我们的拖拽也可以通过修改这两个属性来实现。

然后就是古老的拖拽思路:按下鼠标的时候,记录光标的初始坐标,抬起鼠标的时候,记录光标的结束坐标,然后计算一下得到x、y的“偏移量”,进而修改 marginLeft、marginTop 这两个属性,即可实现拖拽的效果。

核心思路就是这样,剩下的就是细节完善了。

还有一个小问题,拖拽后关闭,然后再次打开,希望可以在拖拽结束的地方打开,而不是默认的位置。所以又想了个办法记录这个位置。

还是要观察 el-dialog 的行为,最后发现规律,一开始 marginLeft 是空的,而拖拽后会保留位置。所以,判断一下就好。

使用方式

原本想直接给el-dialog 设置自定义指令,但是发现“无效”,所以只好在外面套个div。

<template>
  <!--拖拽-->
  <el-button @click="dialog.visible = true">打开</el-button>
  <div v-dialog-drag="dialog" >
    <el-dialog
      v-model="dialog.visible"
      title="自定义拖拽2"
      width="25%"
    >
      <span>拖拽测试</span>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialog.visible = false">Cancel</el-button>
          <el-button type="primary" @click="dialog.visible = false">Confirm</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script lang="ts">
  import { defineComponent, ref, reactive } from 'vue'
  import { _dialogDrag } from '../../../lib/main'
  export default defineComponent({
    name: 'nf-dialog-move',
    directives: {
      dialogDrag: _dialogDrag
    },
    props: {
    },
    setup(props, context) {
      const dialog = reactive({
        visible: false
      })
      return {
        meta,
        dialog
      }
    }
  })
</script>

如果全局注册了自定义指令,那么组件里面就不用注册了。

dialog 的 visible: visible 这个属性的名称被写死了,不能用其他名称。这是一个偷懒的设定。

源码

gitee.com/naturefw-co…

在线演示

naturefw-code.gitee.io/nf-rollup-u…

以上就是vue3使用自定义指令实现el dialog拖拽功能示例详解的详细内容,更多关于vue3指令el dialog拖拽的资料请关注Devmax其它相关文章!

vue3使用自定义指令实现el dialog拖拽功能示例详解的更多相关文章

  1. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  2. HTML5 weui使用笔记

    这篇文章主要介绍了HTML5 weui使用笔记,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  4. Swift 3 popup model dialog传递数据

    弹出的controller:

  5. android – 当应用程序转到后台时阻止Dialog(或DialogFragment)关闭

    我的应用程序向用户显示进度或AlertDialog是很常见的.如果用户将应用程序放入后台然后稍后返回,我希望仍然显示对话框.有没有办法让Android处理这个?

  6. android – Toast与Dialog框:何时使用?

    谢谢.解决方法Toast主要用于告知用户一些不重要且不需要交互的东西,所以我会使用Toastforthethat.此外,Toast不会阻止用户使用设备/应用程序,您仍然可以激活,例如显示Toast时的基础图标.对话框通常要求用户做出选择,或者显示不需要交互的进度但是将使用户在此期间不做其他事情,这可能是重要的,例如,一旦用户在完成参数之前更改参数,您进行的计算将失败.

  7. android – 当从View的LongPress触发DOWN事件时,从Dialog注册UP / CANCEL

    我有一个UX要求,即用户通过长按GridView中的单元格来触发Dialog.>显示对话框时,用户必须能够在屏幕周围移动手指/拇指,而不会在离开GridView单元格边界时触发UP/CANCEL事件.>当用户最终断开与屏幕的联系时,我正在寻找捕获.GridView似乎记录了UP/CANCEL的一些误报,我们没有看到使用任何其他视图.>问题是原始视图捕获所有触摸事件,因为DOWN被它捕获.>在原始视

  8. android – 在片段中实现对话框时添加内容之前必须请求窗口功能

    我有一个片段,我需要在其中显示自定义对话框.请查看下面的代码.删除行时:没有错误,但如果我使用相同的以下错误抛出:解决方法我已经为你的情况实施并尝试了很多替代方案,它的工作非常好,所以我没有机会审查你的错误.但我可以建议你用AlertDialog.Builder替换AppCompatDialog,这是一个android.support.v7.app类.替换此代码同注意:如果您有任何处理对话事件的类

  9. android – 当一个Activity作为Dialog打开时,如何设置一个可取消的false?

    我的主题代码如下:解决方法您可以非常轻松地在Java编码中实现这一点.如果你有一个Activity,那么你应该这样做如果您使用过Dialog类,则应该调用如果要在单击后台活动时阻止关闭它.

  10. 在Android日期选择器中停用未来日期

    大家好:如何在Android中禁用DatePickerDialog中的未来日期.我正在使用以下实现.http://www.androidpeople.com/android-datepicker-dialog-example谢谢Ashwani解决方法您应该可以在DatePickerDialog上调用getDatePicker().setMaxDate(long),将今天设置为最大日期.您可以使用您

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部