Vue 用於可調整大小和可拖動元素的組件並支持組件之間的衝突檢測與組件對齊
17年就應用此組件到了項目中,當時正式版的功能不能知足項目需求,還拉取了dev
分支的測試版進行了簡單的更改。(項目中主要功能之一須要用到此組件)
今年由於需求變動,項目重構(手動淚奔),而後去看了看github
,該組件的正式版本更新到了1.7.x
,因而把正式版拉下來根據本身的需求進行了修改併發布新版到npm上。
新增特徵php
原組件地址:vue-draggable-resizable
新組件地址:vue-draggable-resizablevue
npm install --save vue-draggable-resizable-gorkys
更多API請在項目說明文檔中查看jquery
Demogit
在原組件的Issues
中提出了建議,做者表示不打算讓此組件進行跨越組件以外的操做。
好吧,既然做者不打算進行這方面的支持,那隻好本身動手了。
Fork
項目到本身的倉庫,而後Clone
項目到本地進行修改。github
1.組件之間的衝突檢測npm
兩個組件不容許重疊,若是重疊,將回到移動或縮放前位置
2.組件與組件之間進行對齊(參照Jquery UI的draggable
)併發
用戶移動一個組件到另外一個組件邊緣的時候,進行對齊操做。相似於吸附效果
首先是組件之間的衝突檢測,組件與組件的邊界檢測須要一個標記
進行判斷。測試
先在props
中加入一個isConflictCheck
,讓使用者本身選擇是否使用此功能。
props:{ /* 定義組件是否開啓衝突檢測 */ isConflictCheck: { type: Boolean, default: false } ... }
當咱們拿到isConflictCheck
後,在setConflictCheck
方法中給組件的Dom
設置一個data-*
的屬性。
setConflictCheck: function () { if (this.isConflictCheck) { this.$el.setAttribute('data-is-check', 'true') } else { this.$el.setAttribute('data-is-check', 'false') } }
而後就是如何去檢測組件之間的衝突,代碼以下,此代碼是在測試版本中使用的,看到這些判斷均可怕,爲了頭髮,就沒有去優化了(反正能使用)。
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 } } } } } }, // 衝突檢測
最後就是在中止移動和縮放時調用上面的方法就能夠了(代碼精簡過)。
handleUp: function (e) { this.handle = null if (this.resizing) { this.resizing = false this.conflictCheck() // 衝突檢測 } if (this.dragging) { this.dragging = false this.conflictCheck() // 衝突檢測 } } // 鼠標鬆開
與衝突檢測同樣的套路。優化
先在props
中加入一個snap
,讓使用者本身選擇是否使用此功能。爲了更靈活,這裏多添加了一個snapTolerance
,當調用對齊時,用來設置組件與組件之間的對齊距離,以像素爲單位。
/* 是否開啓元素對齊 */ snap: { type: Boolean, default: false }, /* 當調用對齊時,用來設置組件與組件之間的對齊距離,以像素爲單位。 */ snapTolerance: { type: Number, default: 5, validator: function (val) { return typeof val === 'number' }
而後就是設置
data-*
屬性
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的借鑑了過來。
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 // 對齊目標的left let r = l + p[i].offsetWidth // 對齊目標右側距離窗口的left let t = p[i].offsetTop// 對齊目標的top let b = t + p[i].offsetHeight // 對齊目標右側距離窗口的top 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 } } } } } }, // 檢測對齊元素
好了,最後就是在鼠標移動組件時及時調用就能夠了。
handleMove: function (e) { ... this.snapCheck() this.$emit('dragging', this.left, this.top) } }, // 鼠標移動
此次的修改還算是很是順利,順便還把以前的一些代碼進行了優化。
若是發現什麼bug或者能夠將代碼優化的地方請勞煩告知我。ui