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系列(11) 执行上下文(Execution Contexts)
Jan 15 Javascript
最好用的省市二级联动 原生js实现你值得拥有
Sep 22 Javascript
快速解决jquery之get缓存问题的最简单方法介绍
Dec 19 Javascript
js监控IE火狐浏览器关闭、刷新、回退、前进事件
Jul 23 Javascript
浅析JavaScript事件和方法
Feb 28 Javascript
JavaScript原生对象之Date对象的属性和方法详解
Mar 13 Javascript
react路由配置方式详解
Aug 07 Javascript
jQuery实现获取form表单内容及绑定数据到form表单操作分析
Jul 03 jQuery
layui lay-verify form表单自定义验证规则详解
Sep 18 Javascript
Node.js API详解之 module模块用法实例分析
May 13 Javascript
Vue微信公众号网页分享的示例代码
May 28 Javascript
JavaScript如何使用插值实现图像渐变
Jun 28 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
PHP取得一个类的属性和方法的实现代码
2011/05/22 PHP
php实现无限级分类(递归方法)
2015/08/06 PHP
php获得客户端浏览器名称及版本的方法(基于ECShop函数)
2015/12/23 PHP
ThinkPHP框架里隐藏index.php
2016/04/12 PHP
源码分析 Laravel 重复执行同一个队列任务的原因
2017/12/25 PHP
php正确输出json数据的实例讲解
2018/08/21 PHP
javascript 读取XML数据,在页面中展现、编辑、保存的实现
2009/10/27 Javascript
使用JQuery库提供的扩展功能实现自定义方法
2014/09/09 Javascript
javascript判断移动端访问设备并解析对应CSS的方法
2015/02/05 Javascript
jquery实现树形菜单完整代码
2015/12/29 Javascript
浅谈JS继承_借用构造函数 &amp; 组合式继承
2016/08/16 Javascript
Vue.js系列之vue-router(上)(3)
2017/01/03 Javascript
微信小程序注册60s倒计时功能 使用JS实现注册60s倒计时功能
2017/08/16 Javascript
详解基于React.js和Node.js的SSR实现方案
2019/03/21 Javascript
js实现鼠标拖拽缩放div实例代码
2019/03/25 Javascript
vue实现select下拉显示隐藏功能
2019/09/30 Javascript
原生js实现下拉选项卡
2019/11/27 Javascript
Jquery Datatables的使用详解
2020/01/30 jQuery
使用python调用浏览器并打开一个网址的例子
2014/06/05 Python
python基础 range的用法解析
2019/08/23 Python
Python大数据之使用lxml库解析html网页文件示例
2019/11/16 Python
python3 pathlib库Path类方法总结
2019/12/26 Python
wxpython自定义下拉列表框过程图解
2020/02/14 Python
浅谈SciPy中的optimize.minimize实现受限优化问题
2020/02/29 Python
判断Threading.start新线程是否执行完毕的实例
2020/05/02 Python
利用python汇总统计多张Excel
2020/09/22 Python
python中pivot()函数基础知识点
2021/01/03 Python
35款精致的 CSS3 和 HTML5 网页模板 推荐
2012/08/03 HTML / CSS
Html5无刷新修改browser Url的方法
2014/01/15 HTML / CSS
html5 实现客户端验证上传文件的大小(简单实例)
2016/05/15 HTML / CSS
使用Html5 Stream开发实时监控系统
2020/06/02 HTML / CSS
单位考核聘任报告
2015/03/02 职场文书
2016新教师岗前培训心得体会
2016/01/08 职场文书
2016年小学推普宣传周活动总结
2016/04/06 职场文书
js实现模拟购物商城案例
2021/05/18 Javascript
GitHub上77.9K的Axios项目有哪些值得借鉴的地方详析
2021/06/15 Javascript