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实现tabs选项卡切换效果(自写原生js)
Mar 19 Javascript
javascript时间函数基础介绍
Mar 28 Javascript
jQuery插件MixItUp实现动画过滤和排序
Apr 12 Javascript
jQuery插件jPaginate实现无刷新分页
May 04 Javascript
酷! 不同风格页面布局幻灯片特效js实现
Feb 19 Javascript
Vue组件库发布到npm详解
Feb 17 Javascript
JavaScript基于对象方法实现数组去重及排序操作示例
Jul 10 Javascript
webpack的CSS加载器的使用
Sep 11 Javascript
如何用RxJS实现Redux Form
Dec 29 Javascript
layui 数据表格 点击分页按钮 监听事件的实例
Sep 02 Javascript
VUE.js实现动态设置输入框disabled属性
Oct 28 Javascript
Node.js Domain 模块实例详解
Mar 18 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
《忧国的莫里亚蒂》先导宣传图与STAFF公开
2020/03/04 日漫
如何写php程序?
2006/12/08 PHP
php 将字符串按大写字母分隔成字符串数组
2010/04/30 PHP
php学习之变量的使用
2011/05/29 PHP
PHP Global变量定义当前页面的全局变量实现探讨
2013/06/05 PHP
PHP使用PDO连接ACCESS数据库
2015/03/05 PHP
php下载文件超时时间的设置方法
2016/10/06 PHP
PHP流Streams、包装器wrapper概念与用法实例详解
2017/11/17 PHP
使用laravel和ECharts实现折线图效果的例子
2019/10/09 PHP
laravel 实现根据字段不同值做不同查询
2019/10/23 PHP
javascript attachEvent和addEventListener使用方法
2009/03/19 Javascript
Jquery+JSon 无刷新分页实现代码
2010/04/01 Javascript
输入自动提示搜索提示功能的使用说明:sugggestion.txt
2013/09/02 Javascript
js实现两个值相加alert出来精确到指定位
2013/09/25 Javascript
限制textbox或textarea输入字符长度的JS代码
2013/10/16 Javascript
JavaScript模板引擎用法实例
2015/07/10 Javascript
整理关于Bootstrap过渡动画的慕课笔记
2017/03/29 Javascript
微信小程序支付功能 php后台对接完整代码分享
2018/06/12 Javascript
js中Function引用类型常见有用的方法和属性详解
2019/12/11 Javascript
Node.js API详解之 os模块用法实例分析
2020/05/06 Javascript
django初始化数据库的实例
2018/05/27 Python
实例介绍Python中整型
2019/02/11 Python
python浪漫表白源码
2019/04/05 Python
python实现爬山算法的思路详解
2019/04/09 Python
python GUI库图形界面开发之PyQt5图片显示控件QPixmap详细使用方法与实例
2020/02/27 Python
YOOX台湾:意大利奢侈品电商
2018/10/13 全球购物
SportsDirect.com马来西亚:英国第一体育零售商
2018/11/21 全球购物
餐饮管理自我介绍信
2014/01/15 职场文书
保护水资源的标语
2014/06/17 职场文书
四川省传达学习贯彻党的群众路线教育实践活动总结大会精神新闻稿
2014/10/26 职场文书
辞职信的写法
2015/02/27 职场文书
2015年教师节慰问信
2015/03/23 职场文书
读《教育心理学》心得体会
2016/01/22 职场文书
《比的意义》教学反思
2016/02/18 职场文书
用python实现监控视频人数统计
2021/05/21 Python
Python学习开发之图形用户界面详解
2021/08/23 Python