Vue 用于可调整大小和可拖动元素的组件并支持组件之间的冲突检测与组件对齐
更新2.0版本
说明:组件基于vue-draggable-resizable 进行二次开发
距离上1.7版本 版本的修改已经过去快一年的时间了,原版组件在之前已经更新到了2.0版本。
虽然之前适配过旧版组件,但是因为2.0版本原作者对代码进行了重构,原来修改的代码照搬是不可能的了。
所以也就一直没有将冲突检测 以及吸附对齐 功能适配到2.0版本,最近正好有时间就适配一下。
新增特征
功能预览
项目地址 新组件地址 : vue-draggable-resizable-gorkys
原组件地址:vue-draggable-resizable
如果喜欢该项目,欢迎Star
前言
17年就应用此组件到了项目中,当时正式版的功能不能满足项目需求,还拉取了dev
分支的测试版进行了简单的更改。(项目中主要功能之一需要用到此组件) 今年因为需求变更,项目重构(手动泪奔),然后去看了看github
,该组件的正式版本更新到了1.7.x
,于是把正式版拉下来根据自己的需求进行了修改并发布新版到npm上。
特征
没有依赖
可拖动,可调整大小或者两者都行
拥有用于调整大小的控制点
限制组件调整大小和移动超出父元素
自定义网格移动
将拖动限制为垂直或水平移动安装使用 1 npm install --save vue-draggable-resizable-gorkys
更多API请在项目说明文档中查看
修改过程记录 提出建议
在原组件的Issues
中提出了建议,作者表示不打算让此组件进行跨越组件之外的操作。 好吧,既然作者不打算进行这方面的支持,那只好自己动手了。
需求说明 1.组件之间的冲突检测
两个组件不允许重叠,如果重叠,将回到移动或缩放前位置
2.组件与组件之间进行对齐(参照Jquery UI的draggable
)
用户移动一个组件到另一个组件边缘的时候,进行对齐操作。类似于吸附效果
代码修改 1.组件之间的冲突检测 首先是组件之间的冲突检测,组件与组件的边界检测需要一个标记
进行判断。
先在props
中加入一个isConflictCheck
,让使用者自己选择是否使用此功能。
1 2 3 4 5 6 7 props:{ /* 定义组件是否开启冲突检测 */ isConflictCheck: { type: Boolean, default: false } ... }
当我们拿到isConflictCheck
后,在setConflictCheck
方法中给组件的Dom
设置一个data-*
的属性。
1 2 3 4 5 6 7 setConflictCheck: function () { if (this.isConflictCheck) { this.$el.setAttribute('data-is-check', 'true') } else { this.$el.setAttribute('data-is-check', 'false') } }
然后就是如何去检测组件之间的冲突,代码如下,此代码是在测试版本中使用的,看到这些判断都可怕,为了头发,就没有去优化了(反正能使用)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 conflictCheck: function ( ) { if (this .isConflictCheck) { let p = this .$el.parentNode.childNodes if (p.length > 1 ) { for (let i = 0 ; i < p.length; i++) { if (p[i] !== this .$el && p[i].className !== undefined && p[i].getAttribute('data-is-check' ) !== 'false' ) { let tw = p[i].offsetWidth let th = p[i].offsetHeight let tl = p[i].offsetLeft let tt = p[i].offsetTop if (this .top >= tt && this .left >= tl && tt + th > this .top && tl + tw > this .left || this .top <= tt && this .left < tl && this .top + this .height > tt && this .left + this .width > tl) { this .top = this .restoreY this .left = this .restoreX this .width = this .restoreW this .height = this .restoreH } else if (this .left <= tl && this .top >= tt && this .left + this .width > tl && this .top < tt + th || this .top < tt && this .left > tl && this .top + this .height > tt && this .left < tl + tw) { this .top = this .restoreY this .left = this .restoreX this .width = this .restoreW this .height = this .restoreH } else if (this .top < tt && this .left <= tl && this .top + this .height > tt && this .left + this .width > tl || this .top > tt && this .left >= tl && this .top < tt + th && this .left < tl + tw) { this .top = this .restoreY this .left = this .restoreX this .width = this .restoreW this .height = this .restoreH } else if (this .top <= tt && this .left >= tl && this .top + this .height > tt && this .left < tl + tw || this .top >= tt && this .left <= tl && this .top < tt + th && this .left > tl + tw) { this .top = this .restoreY this .left = this .restoreX this .width = this .restoreW this .height = this .restoreH } else if (this .left >= tl && this .top >= tt && this .left < tl + tw && this .top < tt + th || this .top > tt && this .left <= tl && this .left + this .width > tl && this .top < tt + th) { this .top = this .restoreY this .left = this .restoreX this .width = this .restoreW this .height = this .restoreH } else if (this .top <= tt && this .left >= tl && this .top + this .height > tt && this .left < tl + tw || this .top >= tt && this .left <= tl && this .top < tt + th && this .left + this .width > tl) { this .top = this .restoreY this .left = this .restoreX this .width = this .restoreW this .height = this .restoreH } } } } } },
最后就是在停止移动和缩放时调用上面的方法就可以了(代码精简过)。
1 2 3 4 5 6 7 8 9 10 11 handleUp: function (e ) { this .handle = null if (this .resizing) { this .resizing = false this .conflictCheck() } if (this .dragging) { this .dragging = false this .conflictCheck() } }
2.组件与组件之间进行对齐 与冲突检测一样的套路。
先在props
中加入一个snap
,让使用者自己选择是否使用此功能。为了更灵活,这里多添加了一个snapTolerance
,当调用对齐时,用来设置组件与组件之间的对齐距离,以像素为单位。
1 2 3 4 5 6 7 8 9 10 11 snap: { type: Boolean , default : false }, snapTolerance: { type: Number , default : 5 , validator: function (val ) { return typeof val === 'number' }
然后就是设置data-*
属性
1 2 3 4 5 6 7 setSnap: function ( ) { if (this .snap) { this .$el.setAttribute('data-is-snap' , 'true' ) } else { this .$el.setAttribute('data-is-snap' , 'false' ) } },
再然后就是主要方法snapCheck
的编写。这里我翻看了一下JQuery UI中的draggable
源码,并近乎copy的借鉴了过来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 snapCheck: function ( ) { if (this .snap) { let p = this .$el.parentNode.childNodes if (p.length > 1 ) { let x1 = this .left let x2 = this .left + this .width let y1 = this .top let y2 = this .top + this .height for (let i = 0 ; i < p.length; i++) { if (p[i] !== this .$el && p[i].className !== undefined && p[i].getAttribute('data-is-snap' ) !== 'false' ) { let l = p[i].offsetLeft let r = l + p[i].offsetWidth let t = p[i].offsetTop let b = t + p[i].offsetHeight let ts = Math .abs(t - y2) <= this .snapTolerance let bs = Math .abs(b - y1) <= this .snapTolerance let ls = Math .abs(l - x2) <= this .snapTolerance let rs = Math .abs(r - x1) <= this .snapTolerance if (ts) { this .top = t - this .height } if (bs) { this .top = b } if (ls) { this .left = l - this .width } if (rs) { this .left = r } } } } } },
好了,最后就是在鼠标移动组件时及时调用就可以了。
1 2 3 4 5 6 handleMove: function (e ) { ... this .snapCheck() this .$emit('dragging' , this .left, this .top) } },
总结 这次的修改还算是非常顺利,顺便还把之前的一些代码进行了优化。 如果发现什么bug或者可以将代码优化的地方请劳烦告知我 。