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 相关文章推荐
js 获取服务器控件值的代码
Mar 05 Javascript
ExtJS PropertyGrid中使用Combobox选择值问题
Jun 13 Javascript
js限制文本框只能输入中文的方法
Aug 11 Javascript
通用javascript代码判断版本号是否在版本范围之间
Nov 29 Javascript
BootStrap中关于Select下拉框选择触发事件及扩展
Nov 22 Javascript
详解js的事件代理(委托)
Dec 22 Javascript
jQuery基于Ajax方式提交表单功能示例
Feb 10 Javascript
原生JS获取元素的位置与尺寸实现方法
Oct 18 Javascript
修改UA在PC中访问只能在微信中打开的链接方法
Nov 27 Javascript
JavaScript实现微信红包算法及问题解决方法
Apr 26 Javascript
小程序的上传文件接口的注意要点解析
Sep 17 Javascript
vue-列表下详情的展开与折叠案例
Jul 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
curl实现站外采集的方法和技巧
2014/01/31 PHP
PHP将两个关联数组合并函数提高函数效率
2014/03/18 PHP
PHP中文乱码解决方案
2015/03/05 PHP
php中使用sftp教程
2015/03/30 PHP
PHP判断json格式是否正确的实现代码
2017/09/20 PHP
validator验证控件使用代码
2010/11/23 Javascript
iframe父页面获取子页面参数的方法
2014/02/21 Javascript
jquery中radio checked问题
2015/03/16 Javascript
JavaScript动态改变表格单元格内容的方法
2015/03/30 Javascript
javascript基础语法学习笔记
2016/01/04 Javascript
javascript瀑布流式图片懒加载实例
2020/06/28 Javascript
关于javascript原型的修改与重写(覆盖)差别详解
2016/08/31 Javascript
谈谈JS中常遇到的浏览器兼容问题和解决方法
2016/12/17 Javascript
JavaScript瀑布流布局实现代码
2017/05/06 Javascript
Node.js使用Express.Router的方法
2017/11/14 Javascript
JavaScript实现为事件句柄绑定监听函数的方法分析
2017/11/14 Javascript
微信小程序实现banner图轮播效果
2020/06/28 Javascript
使用vue实现多规格选择实例(SKU)
2019/08/23 Javascript
简单分析js中的this的原理
2019/08/31 Javascript
小程序双头slider选择器的实现示例
2020/03/31 Javascript
[42:48]完美世界DOTA2联赛PWL S3 Magma vs INK ICE 第二场 12.11
2020/12/16 DOTA
Python发送form-data请求及拼接form-data内容的方法
2016/03/05 Python
django admin组件使用方法详解
2019/07/19 Python
详解python中各种文件打开模式
2020/01/19 Python
Pycharm Available Package无法显示/安装包的问题Error Loading Package List解决
2020/09/18 Python
python 决策树算法的实现
2020/10/09 Python
appium+python自动化配置(adk、jdk、node.js)
2020/11/17 Python
CSS3+Sprite实现僵尸行走动画特效源码
2016/01/27 HTML / CSS
欧洲第一中国智能手机和平板电脑网上商店:CECT-SHOP
2018/01/08 全球购物
英国太阳镜品牌:Taylor Morris Eyewear
2018/04/18 全球购物
公司担保书格式范文
2014/05/12 职场文书
党的群众路线教育实践活动个人对照检查材料(校长)
2014/11/05 职场文书
维护民族团结心得体会2016
2016/01/15 职场文书
《抽屉原理》教学反思
2016/02/20 职场文书
MySQL表字段时间设置默认值
2021/05/13 MySQL
Redis读写分离搭建的完整步骤
2021/09/14 Redis