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 相关文章推荐
IE浏览器PNG图片透明效果代码
Sep 02 Javascript
在浏览器窗口上添加遮罩层的方法
Nov 12 Javascript
javascript jscroll模拟html元素滚动条
Dec 18 Javascript
文本框文本自动补全效果示例分享
Jan 19 Javascript
Bootstrap 折叠(Collapse)插件用法实例详解
Jun 01 Javascript
AngularJS  自定义指令详解及实例代码
Sep 14 Javascript
JS动态的把左边列表添加到右边的实现代码(可上下移动)
Nov 17 Javascript
Bootstrap基本组件学习笔记之input输入框组(9)
Dec 07 Javascript
微信小程序 配置文件详细介绍
Dec 14 Javascript
js querySelector() 使用方法
Dec 21 Javascript
jQuery实现选中行变色效果(实例讲解)
Jul 06 jQuery
webpack多入口多出口的实现方法
Aug 17 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实现移除数组中为空或为某值元素的方法
2017/01/07 PHP
Yii2中多表关联查询hasOne hasMany的方法
2017/02/15 PHP
基于prototype的validation.js发布2.3.4新版本,让你彻底脱离表单验证的烦恼
2006/12/06 Javascript
javascript实现划词标记+划词搜索功能
2007/03/06 Javascript
JavaScript 设计模式 富有表现力的Javascript(一)
2010/05/26 Javascript
js列举css中所有图标的实现代码
2011/07/04 Javascript
SyntaxHighlighter语法高亮插件使用说明
2011/08/14 Javascript
JS实现局部选择打印和局部不选择打印
2014/04/03 Javascript
jQuery插件之jQuery.Form.js用法实例分析(附demo示例源码)
2016/01/04 Javascript
KnockoutJS 3.X API 第四章之表单submit、enable、disable绑定
2016/10/10 Javascript
javascript设计模式之中介者模式学习笔记
2017/02/15 Javascript
基于jQuery实现手风琴菜单、层级菜单、置顶菜单、无缝滚动效果
2017/07/20 jQuery
JS库particles.js创建超炫背景粒子插件(附源码下载)
2017/09/13 Javascript
layui加载表格,绑定新增,编辑删除,查看按钮事件的例子
2019/09/06 Javascript
浅谈layui使用模板引擎动态渲染元素要注意的问题
2019/09/14 Javascript
vue如何在用户要关闭当前网页时弹出提示的实现
2020/05/31 Javascript
[00:06]Yes,it worked!小卡尔成功穿越时空加入战场!
2019/07/20 DOTA
Python实现根据指定端口探测服务器/模块部署的方法
2014/08/25 Python
利用Tkinter(python3.6)实现一个简单计算器
2017/12/21 Python
python3获取两个日期之间所有日期,以及比较大小的实例
2018/04/08 Python
Python实现数据可视化看如何监控你的爬虫状态【推荐】
2018/08/10 Python
Python读取系统文件夹内所有文件并统计数量的方法
2018/10/23 Python
如何爬取通过ajax加载数据的网站
2019/08/15 Python
Python操作注册表详细步骤介绍
2020/02/05 Python
Python爬虫之爬取淘女郎照片示例详解
2020/07/28 Python
Python3中对json格式数据的分析处理
2021/01/28 Python
Pretty You London官网:英国拖鞋和睡衣品牌
2019/05/08 全球购物
Eton丹麦官网:精美的男式衬衫
2020/05/27 全球购物
2014年党员公开承诺践诺书
2014/03/25 职场文书
欢度春节标语
2014/07/01 职场文书
见习报告格式要求
2014/11/04 职场文书
房屋授权无偿使用证明
2014/11/29 职场文书
简单了解 MySQL 中相关的锁
2021/05/25 MySQL
使用CSS实现一个搜索引擎的原理解析
2021/09/25 HTML / CSS
《模拟人生4》推出新补丁 “婚礼奇缘”DLC终于得到修复
2022/04/03 其他游戏
解决IIS7下无法绑定https主机的问题
2022/04/29 Servers