vue使用自定义指令实现拖拽


Posted in Javascript onJanuary 29, 2021

需求背景,工作中需要实现一个自定义打印模板的需求,能够实现单个及多个dom元素的同时拖拽,也能通过外部的input元素修改dom元素的样式。在npm和GitHub上找了各种已有的vue组件,不够灵活,效果都不是自己想要的

1.vue自定义指令

Vue.directive('dragx', (el, binding, vnode) => {
 // 默认参数
 let defaultOpts = {
 dragDirection: 'n, e, s, w, ne, se, sw, nw, all',
 dragContainerId: '', //
 dragBarClass: '', // 类选择器
 canDrag: true,
 canResize: true,
 multiSelect: false
 }
 let isMove = false
 let isResize = false
 let constraintDom
 let constraintRect
 let constraintRectHeight
 let constraintRectWidth
 binding.value = binding.value || {}
 let cfg = Object.assign({}, defaultOpts, binding.value)
 if (cfg.dragContainerId) {
 constraintDom = document.querySelector('#' + cfg.dragContainerId)
 constraintRect = constraintDom.getBoundingClientRect()
 constraintRectHeight = constraintRect.height
 constraintRectWidth = constraintRect.width
 }

 let getStyleNumValue = (style, key) => parseInt(style.getPropertyValue(key), 10)
 // 设置约束范围
 function setConstraint (data) {
 if (cfg.dragContainerId) {
 if (data.left <= 0) data.left = 0
 if (data.top <= 0) data.top = 0
 if (data.top + data.height + data.borderTop + data.borderBottom >= constraintRectHeight) data.top = constraintRectHeight - data.height - data.borderTop - data.borderBottom
 if (data.left + data.width + data.borderLeft + data.borderRight > constraintRectWidth) data.left = constraintRectWidth - data.width - data.borderLeft - data.borderRight
 }
 }

 el.onmousemove = e => {
 if (cfg.dragBarClass.length > 0 && e.target.classList.contains(cfg.dragBarClass) && cfg.canDrag) {
 el.style.cursor = 'move'
 return
 }
 el.style.cursor = ''
 }

 el.onmouseleave = e => {
 el.style.cursor = ''
 }

 el.onmousedown = e => {
 let clickId = (e.target || e.srcElement).id
 let posData = {
 x: e.pageX, y: e.pageY
 }
 el.onmouseup = e => {
 // dom元素选中时发送active事件
 if (!cfg.active) el.dispatchEvent(new CustomEvent('bindActive', { 'detail': { ...posData } }))
 }
 isMove = false
 if (cfg.dragBarClass.length > 0 && !e.target.classList.contains('icon-icon-resize')) {
 isMove = true
 isResize = false
 document.body.style.cursor = 'move'
 } else if (e.target.classList.contains('icon-icon-resize')) {
 isMove = false
 isResize = true
 }

 let style
 let rect
 let data
 let tableStyle

 style = window.getComputedStyle(el)
 rect = el.getBoundingClientRect()
 data = {
 width: getStyleNumValue(style, 'width'),
 height: getStyleNumValue(style, 'height'),
 left: getStyleNumValue(style, 'left'),
 top: getStyleNumValue(style, 'top'),
 borderLeft: getStyleNumValue(style, 'border-left-width'),
 borderTop: getStyleNumValue(style, 'border-top-width'),
 borderRight: getStyleNumValue(style, 'border-right-width'),
 borderBottom: getStyleNumValue(style, 'border-bottom-width'),
 deltX: e.pageX - rect.left,
 deltY: e.pageY - rect.top,
 startX: rect.left,
 startY: rect.top
 }
 if (el.id.indexOf(THSIGN) > -1 || el.id.indexOf(TDSIGN) > -1) {
 let table = document.getElementById(el.id.split(SEPARATOR)[0])
 tableStyle = window.getComputedStyle(table)
 data.left = getStyleNumValue(tableStyle, 'left')
 data.top = getStyleNumValue(tableStyle, 'top')
 }

 document.onmousemove = edom => {
 if (edom.ctrlKey) return
 if (isResize) {
 data.width = Math.round(edom.pageX - data.startX + data.borderLeft + data.borderRight)
 data.height = Math.round(edom.pageY - data.startY + data.borderBottom + data.borderTop)
 }
 // 处理组件 移动
 if (isMove && cfg.canDrag && (cfg.active || cfg.multiSelect)) {
 let targetPageX = edom.pageX
 let targetPageY = edom.pageY
 let deltX = targetPageX - data.startX - data.deltX
 let deltY = targetPageY - data.startY - data.deltY
 let newLeft = Math.round(getStyleNumValue(style, 'left') || '0', 10) + deltX
 let newTop = Math.round(getStyleNumValue(style, 'top') || '0', 10) + deltY
 data.left = Math.round(newLeft)
 data.top = Math.round(newTop)
 data.startX = data.startX + deltX
 data.startY = data.startY + deltY
 setConstraint(data)
 if (el.id.indexOf(THSIGN) > -1 || el.id.indexOf(TDSIGN) > -1) {
 let newLeft = Math.round(getStyleNumValue(tableStyle, 'left') || '0', 10) + deltX
 let newTop = Math.round(getStyleNumValue(tableStyle, 'top') || '0', 10) + deltY
 data.left = Math.round(newLeft)
 data.top = Math.round(newTop)
 }
 }
 if (cfg.multiSelect) {
 let domData = {
 el: clickId,
 x: edom.pageX,
 y: edom.pageY
 }
 // 移动多个元素时发送事件 bindUpdateDoms
 el.dispatchEvent(new CustomEvent('bindUpdateDoms', { detail: domData }))
 } else {
 // 移动单个元素移动时发送事件 bindUpdate
 el.dispatchEvent(new CustomEvent('bindUpdate', { detail: data }))
 }

 document.onmouseup = edom => {
 if (cfg.multiSelect && posData.x != edom.pageX && posData.y != edom.pageY) {
 let domData = {
 x: edom.pageX,
 y: edom.pageY
 }
 // 发送下面2个事件主要是为了实现操作的撤销和恢复功能,浏览器保存一个执行栈,一个移动表示一个事件
 // 多个元素同时移动时发送事件 bindFinishMoveDoms
 el.dispatchEvent(new CustomEvent('bindFinishMoveDoms', { detail: domData }))
 } else if (posData.x != edom.pageX && posData.y != edom.pageY) {
 // 单个元素同时移动时发送事件 bindFinishMove
 el.dispatchEvent(new CustomEvent('bindFinishMove', { detail: data }))
 }
 document.body.style.cursor = ''
 document.onmousemove = null
 document.onmouseup = null
 isMove = false
 }
 }

 document.onmouseup = edom => {
 document.body.style.cursor = ''
 document.onmousemove = null
 document.onmouseup = null
 isMove = false
 }
 }
})

2.自定义事件

在vue组件使用自定义指令并添加事件监听器

如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。

<div v-dragx="{ dragBarClass: 'drag', dragContainerId: 'page', multiSelect, active }"
 @bindUpdate="bindUpdate"
 @bindActive="activeDom"
 @bindUpdateDoms="bindUpdateDoms"
 @bindFinishMove="pushStackByDom"
 @bindFinishMoveDoms="pushStackByDoms"
 :style="style"
 :id="uId"
 class="drag-wrap">
</div>
// 添加一个适当的事件监听器
<div @bindUpdate="bindUpdate" />

// 创建并分发事件
el.dispatchEvent(new CustomEvent('bindUpdate', { detail: data }))

字段说明

vue使用自定义指令实现拖拽

<template>
 <div v-dragx="{ dragBarClass: 'drag', dragContainerId: 'page', multiSelect, active }"
 @bindUpdate="bindUpdate"
 @bindActive="activeDom"
 @bindUpdateDoms="bindUpdateDoms"
 @bindFinishMove="pushStackByDom"
 @bindFinishMoveDoms="pushStackByDoms"
 :style="style"
 :id="uId"
 class="drag-wrap">
 <slot></slot>
 <span class="iconfont icon-icon-resize"></span>
 </div>
</template>
<script>
export default {
 data () {
 return {
 prevSize: {}
 }
 },
 props: {
 item: {
 type: Object,
 default: () => ({})
 },
 isVariable: {
 type: Boolean,
 default: false
 },
 active: {
 type: Boolean,
 default: false
 },
 size: {
 type: Object,
 default: () => ({
 x: 200,
 y: 200,
 w: 200,
 h: 24
 })
 },
 multiSelect: {
 type: Boolean,
 default: false
 },
 uId: {
 type: String,
 required: true
 },
 dragContainerId: {
 type: String
 }
 },
 computed: {
 style: function () {
 return {
 top: this.size.y + 'px',
 left: this.size.x + 'px',
 width: this.size.w + 'px',
 height: this.size.h + 'px'
 }
 }
 },
 methods: {
 activeDom (e) {
 this.$emit('activated', this.uId, e.detail.x, e.detail.y)
 this.prevSize = {
 ...this.size
 }
 },
 bindUpdate (event) {
 let data = event.detail
 this.size.x = data.left
 this.size.y = data.top

 this.$store.dispatch('RESIZE_ID', { id: this.uId, ...data })
 this.$emit('resizing', { ...data })
 },
 bindUpdateDoms (e) {
 this.$emit('movingDoms', e.detail)
 },
 pushStackByDom (e) {
 this.$store.dispatch('RESIZE_ID', { prevSize: this.prevSize, id: this.uId, ...event.detail })
 },
 pushStackByDoms (e) {
 this.$emit('finishMoveDoms', e.detail)
 }
 }
}
</script>
<style scoped>
.drag {
 background-color: #ccc;
 border-top: solid 1px rgba(33, 61, 223, 0.541);
 border-bottom: solid 1px rgba(33, 61, 223, 0.541);
}
.drag-wrap {
 position: absolute;
}
.icon-icon-resize {
 position: absolute;
 right: 0;
 bottom: 0;
 line-height: 12px;
 cursor: se-resize;
 font-size: 12px;
 color: #666;
}
</style>

3.vuex实现状态管理

因为涉及到多个组件的通信,因此使用vuex,把模板信息和执行栈全部保存在vuex中

最终实现的效果如下图所示

vue使用自定义指令实现拖拽

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript实现的鼠标链接提示效果生成器代码
Jun 28 Javascript
初窥JQuery(二)事件机制(2)
Dec 06 Javascript
围观tangram js库
Dec 28 Javascript
JavaScript编程中容易出BUG的几点小知识
Jan 31 Javascript
分享有关jQuery中animate、slide、fade等动画的连续触发、滞后反复执行的bug
Jan 10 Javascript
jQuery版本升级踩坑大全
Jan 12 Javascript
Node.js开发者必须了解的4个JS要点
Feb 21 Javascript
jquery zTree异步加载简单实例讲解
Feb 25 Javascript
JavaScript实现类似拉勾网的鼠标移入移出效果
Oct 27 Javascript
页面点击小红心js实现代码
May 26 Javascript
ElementUI Tag组件实现多标签生成的方法示例
Jul 08 Javascript
layui 实现table翻页滚动条位置保持不变的例子
Sep 05 Javascript
对TypeScript库进行单元测试的方法
Jul 18 #Javascript
基于JS实现数字动态变化显示效果附源码
Jul 18 #Javascript
微信小程序实现拍照画布指定区域生成图片
Jul 18 #Javascript
vue使用video.js进行视频播放功能
Jul 18 #Javascript
百度小程序之间的页面通信过程详解
Jul 18 #Javascript
微信小程序如何获取群聊的openGid以及名称详解
Jul 17 #Javascript
vue+django实现一对一聊天功能的实例代码
Jul 17 #Javascript
You might like
一个数据采集类
2007/02/14 PHP
生成ubuntu自动切换壁纸xml文件的php代码
2010/07/17 PHP
PHP mysql与mysqli事务使用说明 分享
2013/08/17 PHP
php制作unicode解码工具(unicode编码转换器)代码分享
2013/12/24 PHP
什么情况下可以不写PHP的闭合标签“?&gt;”
2014/08/28 PHP
PHP快速排序quicksort实例详解
2016/09/28 PHP
详解jquery uploadify 上传文件
2013/11/09 Javascript
JavaScript页面模板库handlebars的简单用法
2015/03/02 Javascript
ClearTimeout消除闪动实例代码
2016/02/29 Javascript
JS判断iframe是否加载完成的方法
2016/08/03 Javascript
async/await与promise(nodejs中的异步操作问题)
2017/03/03 NodeJs
ajax +NodeJS 实现图片上传实例
2017/06/06 NodeJs
浅谈Vue父子组件和非父子组件传值问题
2017/08/22 Javascript
JS Testing Properties 判断属性是否在对象里的方法
2017/10/01 Javascript
AngularJS双向数据绑定原理之$watch、$apply和$digest的应用
2018/01/30 Javascript
微信小程序App生命周期详解
2018/01/31 Javascript
详解无限滚动插件vue-infinite-scroll源码解析
2019/05/12 Javascript
vue使用混入定义全局变量、函数、筛选器的实例代码
2019/07/29 Javascript
jQuery实现电梯导航模块
2020/12/22 jQuery
Python操作Mysql实例代码教程在线版(查询手册)
2013/02/18 Python
Python中Collection的使用小技巧
2014/08/18 Python
Python中的index()方法使用教程
2015/05/18 Python
在Python中操作字典之clear()方法的使用
2015/05/21 Python
解析Python中的eval()、exec()及其相关函数
2017/12/20 Python
Python模块WSGI使用详解
2018/02/02 Python
解决安装pycharm后不能执行python脚本的问题
2019/01/19 Python
对python中类的继承与方法重写介绍
2019/01/20 Python
python 求一个列表中所有元素的乘积实例
2019/06/11 Python
python的re模块使用方法详解
2019/07/26 Python
Python列表list常用内建函数实例小结
2019/10/22 Python
Python 打印自己设计的字体的实例讲解
2021/01/04 Python
python 逆向爬虫正确调用 JAR 加密逻辑
2021/01/12 Python
HTML5 Canvas画线技巧——实现绘制一个像素宽的细线
2013/08/02 HTML / CSS
英国领先的运动物理治疗供应公司:Vivomed
2018/07/14 全球购物
大唐电信科技股份有限公司java工程师面试经历
2016/12/09 面试题
机工车间主任岗位职责
2014/03/05 职场文书