vue3使用自定义指令实现el dialog拖拽功能示例详解

目录
  • 实现el-dialog的拖拽功能
  • 通过自定义指令实现拖拽功能
  • 实现拖拽功能
  • 使用方式

实现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拖拽的资料请关注我们其它相关文章!

(0)

相关推荐

  • vue3 name 属性的使用技巧详解

    目录 引言 使用步骤 引言 如果你在 vue3 开发中使用了 <script setup> 语法的话,对于组件的 name 属性,需要做一番额外的处理. 对于 vue@3.2.34 及以上版本,在使用 <script setup> 的单文件组件时,vue 会根据组件文件名,自动推导出 name 属性.也就是名为 MyComponent.vue 或 my-component.vue 的文件, name 属性为 MyComponent,而当你在组件内显示定义 name 属性时,会覆盖推

  • Vue3 构建 Web Components使用详解

    目录 引言 构建 Web Components 属性 事件 插槽 子组件样式问题 方法 总结 引言 有时候想写一个无关框架组件,又不想用原生或者 Jquery 那套去写,而且还要避免样式冲突,用 Web Components 去做刚觉就挺合适的.但是现在 Web Components 使用起来还是不够灵活,很多地方还是不太方便的,如果能和 MVVM 搭配使用就好了. 早在之前 Angular 就支持将组件构建成 Web Components,Vue3 3.2+ 开始终于支持将组建构建成 Web

  • Vue3 源码解读之 Teleport 组件使用示例

    目录 Teleport 组件解决的问题 Teleport 组件的基本结构 Teleport 组件 process 函数 Teleport 组件的挂载 Teleport 组件的更新 moveTeleport 移动Teleport 组件 hydrateTeleport 服务端渲染 Teleport 组件 总结 Teleport 组件解决的问题 版本:3.2.31 如果要实现一个 “蒙层” 的功能,并且该 “蒙层” 可以遮挡页面上的所有元素,通常情况下我们会选择直接在 标签下渲染 “蒙层” 内容.如果

  • vue3 Vite 进阶rollup命令行使用详解

    目录 rollup介绍 以命令行方式打包 Tree Shaking Rollup 的命令行使用 命令行 format 格式 rollup.config.js 设置/获取环境变量 插件 plugins rollup介绍 开源类库优先选择 以 ESM 标准为目标的构建工具 Tree Shaking 以命令行方式打包 安装 rollup npm install -g rollup 创建 index.js 文件 import path from "path"; console.log(&quo

  • 详解vue3中如何使用shaka-player

    目录 正文 开始使用 做成组件shakaPlayer 使用方式 注意事项 正文 Shaka Player 是谷歌公司对外开源的一款 JavaScript 类库,详细请看谷歌官方API文档. 开始使用 我们可以使用 npm 下载 npm i shaka-player 做成组件shakaPlayer <script setup> import { ref, watch, onMounted, onBeforeUnmount } from "vue"; import shaka

  • Vue3中使用pnpm搭建monorepo开发环境

    目录 前言 Pnpm 和 Monorepo 搭建开发环境 创建项目 配置 monorepo 安装依赖 初始化Typescript 准备两个模块 shared reactivity 编写构建脚本 完成第一次调试 小结 前言 Vue3 源码阅读系列,计划从环境搭建开始,将 Vue3 的响应式模块,运行时模块和编译器模块,以及状态库 Pinia.路由库 Vue-Router的核心原理做一个梳理.这大概是一个漫长的过程.祝自己不要烂尾,祝大家有所收获. Pnpm 和 Monorepo Pnpm 是新一代

  • 详解vue3中如何使用youtube-player

    目录 正文 开始使用 做成组件youtubePlayer 使用方式 注意事项 常用参数 常用API 正文 youtube-player 是 YouTube IFrame Player API (YIPA) 的封装.可以在自己网站上播放YouTube视频. 开始使用 使用 npm 下载 npm i youtube-player 做成组件youtubePlayer <script setup> import { ref, watch, onMounted, onBeforeUnmount } fr

  • vue3使用自定义指令实现el dialog拖拽功能示例详解

    目录 实现el-dialog的拖拽功能 通过自定义指令实现拖拽功能 实现拖拽功能 使用方式 实现el-dialog的拖拽功能 这里指的是 element-plus 的el-dialog组件,一开始该组件并没有实现拖拽的功能,当然现在可以通过设置属性的方式实现拖拽. 自带的拖拽功能非常严谨,拖拽时判断是否拖拽出窗口,如果出去了会阻止拖拽. 如果自带的拖拽功能可以满足需求的话,可以跳过本文. 通过自定义指令实现拖拽功能 因为要自己操作dom(设置事件),所以感觉还是使用自定义指令更直接一些,而且对原

  • JS面向对象编程实现的拖拽功能案例详解

    本文实例讲述了JS面向对象编程实现的拖拽功能.分享给大家供大家参考,具体如下: 原始的面向过程代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <style> #box { width: 100px; height: 100px; background: blue; position: absolute; } </style> <title>

  • vue移动端写的拖拽功能示例代码

    相关知识点 touchstart 当在屏幕上按下手指时触发 touchmove 当在屏幕上移动手指时触发 touchend 当在屏幕上抬起手指时触发 mousedown mousemove mouseup对应的是PC端的事件 touchcancel 当一些更高级别的事件发生的时候(如电话接入或者弹出信息)会取消当前的touch操作,即触发 touchcancel.一般会在touchcancel时暂停游戏.存档等操作. 效果图 实现步骤html 总结了一下评论,好像发现大家都碰到了滑动的问题.就在

  • 基于javascript的拖拽类封装详解

    效果图如下 github地址如下: github地址 使用方法 引入js和对应的css import Drag from '../../static/dragger.js' import './assets/css/dragger.css' 之后,实例化 new Drag({ id: 'box-dragger', showAngle: true, isScale: false, showBorder: false }) new Drag({ id: 'box-dragger2', canZoom

  • React实现卡片拖拽效果流程详解

    前提摘要: 学习宋一玮 React 新版本 + 函数组件 &Hooks 优先 开篇就是函数组件+Hooks 实现的效果如下: 学到第11篇了 照葫芦画瓢,不过老师在讲解的过程中没有考虑拖拽目标项边界问题,我稍微处理了下这样就实现拖拽流畅了 下面就是主要的代码了,实现拖拽(src/App.js): 核心在于标记当前项,来源项,目标项,并且在拖拽完成时对数据处理,更新每一组数据(useState): /** @jsxImportSource @emotion/react */ // 上面代码是使用e

  • Vue自定义指令拖拽功能示例

    下面给大家分享vue自定义指令拖拽功能代码,具体代码如下所示: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>实例方法</title> <meta name="viewport" content="width=device-width, initial-scale=1

  • vue3 elmentPlus table实现列宽可拖拽功能

    el-table 里边的border属性,设置之后表格即可直接拖拽,下文内容是相关扩展功能的实现 最近公司项目里边需求要让表格的宽度可拖动,我们的公司的项目有vue2的也有vue3的,表格分别使用了element UI和element Plus,前者的社区比较丰富,我们使用了mizuka-wu/el-table-draggable 但是对于后者,我查到社区相关的插件并不多,但也找到了guolaopi/element-plus-table-dragable-demo 把demo下载下来之后,我发现

  • vue2.0使用Sortable.js实现的拖拽功能示例

    简介 在使用vue1.x之前的版本的时候,页面中的拖拽功能,我在项目中是直接用的jquery ui中的sortable.js,只是在拖拽完成后,在update的回调函数中又重新排序了存放数据的数组.但是当把vue升级到2.0以上后发现拖拽功能失效了,于是使用了下面代码. 该案例主要是在用于vuejs2.0中实现的拖拽功能,用到的的js有Sortable.js,vuedraggable.js,当然还有vue.min.js,提供的案例使用的require.js加载. 实现效果 实现后的效果如图所示:

  • Android利用RecyclerView实现全选、置顶和拖拽功能示例

    前言 今天给大家分享是如何在RecyclerView实现全选,ItemTouchHelper实现侧滑删除,拖拽功能.比较基础.关于RecyclerView的强大,就不多说了.在Android L SDK发布的新API中最有意思的就是RecyclerView 和 CardView了, 按照官方的说法, RecyclerView 一个ListView 的一个更高级更灵活的一个版本, 可以自定义的东西太多了. 效果: RecyclerView实现全选,ItemTouchHelper实现侧滑删除,拖拽功

  • AngularJS实现的简单拖拽功能示例

    本文实例讲述了AngularJS实现的简单拖拽功能.分享给大家供大家参考,具体如下: <!DOCTYPE html> <html ng-app="myApp"> <head> <meta charset="UTF-8"> <title>www.jb51.net AngularJS拖拽</title> <style> *{ padding:0; margin:0; } .wei{ wi

随机推荐